zoukankan      html  css  js  c++  java
  • 【洛谷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;
    }
    
  • 相关阅读:
    网上搜的逆元知识
    关于树的一点学习【清北学堂】
    矩阵乘法和斐波那契数列【清北学堂】
    Luogu【P1130】红牌(DP)
    Luogu【P1901】发射站(单调栈)
    Oracle_PL/SQL(1) 匿名块
    Oracle_SQL(7) 复杂查询
    Oracle_SQL(6) 单行函数
    Oracle_SQL(5) 连接和子查询
    Oracle_SQL(4) DDL 表和约束
  • 原文地址:https://www.cnblogs.com/stoorz/p/15567398.html
Copyright © 2011-2022 走看看