【洛谷P3992】开车

题目

题目链接:https://www.luogu.com.cn/problem/P3992
\(n\) 辆车,分别在 \(a_1, a_2, \ldots , a_n\) 位置和 \(n\) 个加油站,分别在 \(b_1, b_2, \ldots ,b_n\) 位置。
每个加油站只能支持一辆车的加油,所以你要把这些车开到不同的加油站加油。一个车从 \(x\) 位置开到 \(y\) 位置的代价为 \(|x-y|\),问如何安排车辆,使得代价之和最小。
同时你有 \(q\) 个操作,每次操作会修改第 \(i\) 辆车的位置到 \(x\),你要回答每次修改操作之后最优安排方案的总代价。
\(n,q\leq 5\times 10^4\)\(0\leq a_i,b_i\leq 10^9\)

思路

很显然最优的匹配方式是将 \(a\)\(b\) 分别排序后一一对应。但是这样并不好搞修改操作,因为一次修改可能会更改很多的匹配,而更改匹配后每一个位置的差的绝对值只能分开来考虑。
把初始和以后修改到的所有位置都存下来离散化,记 \(w_i\) 表示离散化后位置 \(i\) 与位置 \(i+1\) 之间的实际距离,\(s_i\) 表示位置 \(i\) 前面车辆的数量减去加油站的数量。那么答案其实就是

\[\sum^{cnt}_{i=1}|s_i|\times w_i \]

因为在最优匹配方式下,前 \(i\) 个位置的车和加油站一定是两两匹配,多出来的 \(|s_i|\) 个车(或者加油站)只能往后匹配,那么位置 \(i\) 与位置 \(i+1\) 这一条边的贡献就是 \(|s_i|\)
这个东西虽然也有绝对值,但是观察到一次修改相当于把一段区间的 \(s\) 全部 \(+1\)\(-1\),这个东西明显可做多了。
考虑分块,对每一个块的 \(s\) 新开一个数组 \(t\) 排序,修改的时候整块直接打 tag,零散的块暴力修改 \(a\) 的值,然后把块重构。
对于一次询问,枚举每一个块,可以在 \(t\) 上二分找到一个邻界点 \(p\),这个块中在 \(p\) 前面的所有元素都满足 \(t_i+\) 这个块的 tag \(<0\)\(p\) 后面的所有元素都满足 \(t_i+\) 这个块的 tag \(\geq 0\)。那么就可以把绝对值拆开了。
每一个块暴力重构的时候顺便求出前缀的 \(w\) 之和,以及 \(|t_i|\times w\) 之和即可。
时间复杂度 \(O(n\sqrt{n}\log n)\)

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=150010,B=350;
int n,m,lim,Q,X[N],Y[N],a[N],b[N],c[N],w[N],bel[N],L[N/B+10],R[N/B+10],add[N/B+10];
ll res[N],sum[N];

struct node
{
	int x,y;
}qry[N];

bool cmp(int x,int y)
{
	return a[x]<a[y];
}

void build(int i)
{
	sort(b+L[i],b+R[i]+1,cmp);
	res[L[i]]=1LL*a[b[L[i]]]*w[b[L[i]]];
	sum[L[i]]=w[b[L[i]]];
	for (int j=L[i]+1;j<=R[i];j++)
	{
		res[j]=res[j-1]+1LL*a[b[j]]*w[b[j]];
		sum[j]=sum[j-1]+w[b[j]];
	}
}

void update(int l,int r,int v)
{
	int bl=bel[l],br=bel[r];
	if (bl==br)
	{
		for (int i=l;i<=r;i++) a[i]+=v;
		build(bl); return;
	}
	for (int i=bl+1;i<br;i++) add[i]+=v;
	for (int i=l;i<=R[bl];i++) a[i]+=v;
	for (int i=r;i>=L[br];i--) a[i]+=v;
	build(bl); build(br);
}

void query()
{
	ll ans=0;
	for (int i=1;i<=lim;i++)
	{
		int l=L[i],r=R[i],mid;
		while (l<=r)
		{
			mid=(l+r)>>1;
			if (a[b[mid]]+add[i]>=0) r=mid-1;
				else l=mid+1;
		}
		int p=r+1;
		if (p==L[i])
			ans+=res[R[i]]+sum[R[i]]*add[i];
		else
		{
			ans+=res[R[i]]-res[p-1]+(sum[R[i]]-sum[p-1])*add[i];
			ans-=res[p-1]+sum[p-1]*add[i];
		}
	}
	printf("%lld\n",ans);
}

int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
		scanf("%d",&X[i]),c[++m]=X[i];
	for (int i=1;i<=n;i++)
		scanf("%d",&Y[i]),c[++m]=Y[i];
	scanf("%d",&Q);
	for (int i=1;i<=Q;i++)
	{
		scanf("%d%d",&qry[i].x,&qry[i].y);
		c[++m]=qry[i].y;
	}
	sort(c+1,c+1+m);
	m=unique(c+1,c+1+m)-c-1;
	for (int i=1;i<=m;i++) w[i]=c[i+1]-c[i];
	for (int i=1;i<=n;i++)
	{
		X[i]=lower_bound(c+1,c+1+m,X[i])-c;
		Y[i]=lower_bound(c+1,c+1+m,Y[i])-c;
		a[X[i]]++; a[Y[i]]--;
	}
	n=m; lim=(n-1)/B+1;
	for (int i=1;i<=n;i++)
		a[i]+=a[i-1],b[i]=i;
	for (int i=1;i<=lim;i++)
	{
		L[i]=R[i-1]+1; R[i]=min(n,i*B);
		for (int j=L[i];j<=R[i];j++) bel[j]=i;
		build(i);
	}
	query();
	for (int i=1;i<=Q;i++)
	{
		qry[i].y=lower_bound(c+1,c+1+m,qry[i].y)-c;
		if (X[qry[i].x]<qry[i].y) update(X[qry[i].x],qry[i].y-1,-1);
		if (X[qry[i].x]>qry[i].y) update(qry[i].y,X[qry[i].x]-1,1);
		X[qry[i].x]=qry[i].y;
		query();
	}
	return 0;
}
原文地址:https://www.cnblogs.com/stoorz/p/15567398.html