【洛谷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\) 前面车辆的数量减去加油站的数量。那么答案其实就是
因为在最优匹配方式下,前 \(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;
}