[bzoj4908][BeiJing2017]开车

来自FallDream的博客,未经允许,请勿转载,谢谢。


你有n辆车,分别a1, a2, ..., an位置和n个加油站,分别在b1, b2, ... ,bn 。每个加油站只能支持一辆车的加油,所以你要把这些车开到不同的加油站加油。一个车从x位置开到y位置的代价为 |x-y| ,问如何安排车辆,使得代价之和最小。同时你有q个操作,每次操作会修改第i辆车的位置到x,你要回答每次修改操作之后最优安排方案的总代价。
 
n,m<=50000
 
首先排序之后一一匹配肯定是最优的。
把点离散之后,车看作1,加油站看作-1,求前缀和。对于离散后的每个点,如果它的前缀和不为0,那么显然有些车/加油站不能匹配,产生的代价是前缀和的绝对值乘以这个点到下个点的距离。
考虑分块维护这东西。对离散的点分根号个块,每一块都自己做一遍,处理出这个块在块之前的点前缀和取不同的值时会产生的代价。由于一个块自己的前缀和不超过根号n,所以只处理前缀和在正负根号n以内的即可,大于根号n或者小于负根号n的,额外代价加入的值相同。处理这个代价可以差分再差分,最后前缀和再前缀和,具体实现可以见代码,处理一个块复杂度$O(\sqrt{n})$。
然后对于修改操作,中间的整块移动一下重新统计答案,两遍的块重构即可。
复杂度$O(n\sqrt{n})$
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define getchar() (*S++)
#define pa pair<int,int>
#define ll long long
#define MN 50000
#define MB 400
char B[1<<26],*S=B;
using namespace std;
inline int read()
{
    int x = 0 , f = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){ if(ch == '-') f = -1;  ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
    return x * f;
}
long long Ans,ans[MB+5],F[MB+5][MB*2+5],f[MB+5][MB*2+5];
int n,m,l[MN*3+5],cnt=0,pos[MB+5],a[MN+5],block[MN*3+5],v[MN*3+5];
int s[MN*3+5],b[MN+5],tot=1,size;
struct ques{int x,y;}q[MN+5];
inline int abs(int x){return x<0?-x:x;}
void Move(int x,int y)
{
    Ans-=ans[x];
    if(abs(y)<=MB) Ans+=(ans[x]=F[x][MB+y]);
    else
    {
        if(y>MB) Ans+=(ans[x]=F[x][MB<<1]+1LL*(y-MB)*f[x][MB<<1]);
        if(y<MB) Ans+=(ans[x]=F[x][0]+1LL*(MB-y)*f[x][0]);
    }
}

void Build(int x)
{
    int W=0;
    for(int i=0;i<=MB<<1;++i) f[x][i]=F[x][i]=0;
    for(int i=(x-1)*size+1;block[i]==x;++i)
    {
        F[x][MB]+=1LL*v[i]*abs(W+=s[i]);
        if(!W)  f[x][1+MB]+=v[i],f[x][MB-1]+=v[i];
        if(W>0) f[x][MB+1]+=v[i],W<MB?(f[x][MB-W-1]+=v[i]<<1):0,f[x][MB-1]-=v[i];
        if(W<0) f[x][MB+1]-=v[i],f[x][MB-W+1]+=v[i]<<1,f[x][MB-1]+=v[i];
    }
    for(int i=1;i<=MB;++i) 
        F[x][MB+i]=F[x][MB+i-1]+(f[x][MB+i]+=f[x][MB+i-1]),
        F[x][MB-i]=F[x][MB-i+1]+(f[x][MB-i]+=f[x][MB-i+1]);                
    Move(x,pos[x]);
}

int main()
{
    fread(B,1,1<<26,stdin);
    n=read();
    for(int i=1;i<=n;++i) a[i]=l[++cnt]=read();
    for(int i=1;i<=n;++i) b[i]=l[++cnt]=read();
    m=read();
    for(int i=1;i<=m;++i) q[i].x=read(),q[i].y=l[++cnt]=read();
    sort(l+1,l+cnt+1);
    for(int i=2;i<=cnt;++i)    if(l[i]!=l[i-1]) l[++tot]=l[i];
    for(int i=1;i<tot;++i) v[i]=l[i+1]-l[i];
    size=sqrt(tot);
    for(int i=1;i<=n;++i) 
        a[i]=lower_bound(l+1,l+tot+1,a[i])-l,
        b[i]=lower_bound(l+1,l+tot+1,b[i])-l,
        ++s[a[i]],--s[b[i]];
    for(int i=1;i<=tot;++i) block[i]=(i-1)/size+1;
    for(int i=1,w=0;i<=block[tot];++i) 
    {
        pos[i]=w;Build(i);
        for(int j=(i-1)*size+1;block[j]==i;++j) w+=s[j];
    }
    printf("%lld\n",Ans);
    for(int i=1;i<=m;++i)
    {
        q[i].y=lower_bound(l+1,l+tot+1,q[i].y)-l; 
        if(q[i].y!=a[q[i].x])
        {                
            --s[a[q[i].x]];++s[q[i].y];
            if(block[q[i].y]==block[a[q[i].x]])
                Build(block[q[i].y]);
            else
            {
                int From=min(block[a[q[i].x]],block[q[i].y]),
                      To=max(block[a[q[i].x]],block[q[i].y]);
                for(int j=From+1;j<To;++j) 
                    Move(j,pos[j]+=(q[i].y>a[q[i].x]?-1:1));
                if(a[q[i].x]>q[i].y) ++pos[block[a[q[i].x]]];
                else --pos[block[q[i].y]];
                Build(block[q[i].y]);Build(block[a[q[i].x]]);
            }
        }
        a[q[i].x]=q[i].y;printf("%lld\n",Ans);
    }
    return 0;
}
posted @ 2017-06-03 14:17  FallDream  阅读(503)  评论(0编辑  收藏  举报