NKOJ 2936 (BZOJ 2001)城市建设(CDQ分治+LCT)

P2936【FJ Training 2014 Day2】城市建设

问题描述

PS国是一个拥有诸多城市的大国,国王Louis为城市的交通建设可谓绞尽脑汁。Louis可以在某些城市之间修建道路,在不同的城市之间修建道路需要不同的花费。Louis希望建造最少的道路使得国内所有的城市连通。但是由于某些因素,城市之间修建道路需要的花费会随着时间而改变,Louis会不断得到某道路的修建代价改变的消息,他希望每得到一条消息后能立即知道使城市连通的最小花费总和,Louis决定求助于你来完成这个任务。
因版权问题,题目已隐藏。如有需要请私下联系root或nodgd。

输入格式

第一行包含三个整数N,M,Q,分别表示城市的数目,可以修建的道路个数,及收到的消息个数。
接下来有M行,第i+1行有三个用空格隔开的整数Xi,Yi,Zi(1<=Xi,Yi<=N, 0<=Zi<=5*107),表示在城市Xi与城市Yi之间修建道路的代价为Zi。接下来Q行,每行包含两个数k,d,表示输入的第k个道路的修建代价修改为d(即将Zi修改为d)。

输出格式

包含Q行,第i行输出得知前i条消息后使城市连通的最小花费总和。

样例输入

5 5 3
1 2 1
2 3 2
3 4 3
4 5 4
5 1 5
1 6
1 1
5 3

样例输出

14
10
9

提示

对于20%的数据, n≤1000,m≤6000,Q≤6000。
另有20%的数据,n≤1000,m≤50000,Q≤8000,修改后的代价不会比之前的代价低。
对于100%的数据, n≤20000,m≤50000,Q≤50000。


题目让维护一个边权不断变化的动态最小生成树。容易发现修改边权相当于删除一条边再添加一条边。
容易发现LCT可以轻松维护加边操作,但无法维护删边操作。
此时考虑用CDQ分治去掉删边操作。
预处理每条边存在的时间,按时间分治,每次将覆盖了整个左区间或右区间的边插入到左区间或右区间的LCT中。分治底层就是每一个时刻的答案LCT。
但是并不能开 nlogn n log ⁡ n 个LCT,空间承受不了,容易发现LCT上的操作是可以撤销的,LINK和CUT互为逆操作,因此只需要用栈记录一下操作,回溯的时候撤销就行。这样就只需要开一颗全局LCT。

最终时间复杂度 O(2mlogqlog(2n)) O ( 2 m log ⁡ q log ⁡ ( 2 n ) ) ,加上LCT的大常数,导致这样做非常地卡常,想要通过此题需要优秀的常数。

另外本题有另一个利用MST性质的做法,大致是利用MST将边分成三类同时缩点,不断缩小边集和点集。时间复杂度一样,但常数小。


代码(常数巨大以至于不能AC):

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#define N 800005
#define ll long long
using namespace std;
struct node{int x,y,z,id;}E[N],LE[N],RE[N];
int n,m,q,L[N],R[N],TOT,tot,Hash[N],la[N];
int ls[N],rs[N],fa[N],Max[N],id[N],v[N],rev[N],S[N],top;
ll ans,Ans[N],Atop;
node AS[N];
inline char getc()
{
    static char *SS,*TT,buf[N]; 
    if(SS==TT)
    {
        TT=(SS=buf)+fread(buf,1,N,stdin);
        if(SS==TT)return EOF;
    }
    return *SS++;
}
inline bool isdigit(char x)
{return '0'<=x&&x<='9';}
inline int read()
{
    static char ch;
    static int D;
    while(!isdigit(ch=getc()));
    for(D=ch-'0';isdigit(ch=getc());)D=D*10+ch-'0';
    return D;
}  
int GM(int x,int y,int z)
{
    if(x>=y&&x>=z)return x;
    if(y>=z)return y;
    return z;
}
bool Isroot(int x)
{return ls[fa[x]]!=x&&rs[fa[x]]!=x;}
void MT(int p)
{
    Max[p]=GM(v[p],Max[ls[p]],Max[rs[p]]);
    if(Max[p]==v[p])id[p]=p;
    else if(Max[p]==Max[ls[p]])id[p]=id[ls[p]];
    else id[p]=id[rs[p]];
}
void PD(int p)
{
    if(rev[p])
    {
        swap(ls[p],rs[p]);
        rev[ls[p]]^=1;
        rev[rs[p]]^=1;
        rev[p]^=1;
    }
}
void Zig(int x)
{
    int y=fa[x],z=fa[y];
    if(!Isroot(y))y==ls[z]?ls[z]=x:rs[z]=x;fa[x]=z;
    ls[y]=rs[x];fa[rs[x]]=y;
    rs[x]=y;fa[y]=x;
    MT(y);MT(x);
}
void Zag(int x)
{
    int y=fa[x],z=fa[y];
    if(!Isroot(y))y==ls[z]?ls[z]=x:rs[z]=x;fa[x]=z;
    rs[y]=ls[x];fa[ls[x]]=y;
    ls[x]=y;fa[y]=x;
    MT(y);MT(x);
}
void Splay(int x)
{
    int i,y,z;
    S[++top]=x;
    for(i=x;!Isroot(i);i=fa[i])S[++top]=fa[i];
    while(top)PD(S[top--]);
    while(!Isroot(x))
    {
        y=fa[x];z=fa[y];
        if(!Isroot(y))
        {
            if(y==ls[z])x==ls[y]?(Zig(y),Zig(x)):(Zag(x),Zig(x));
            else x==rs[y]?(Zag(y),Zag(x)):(Zig(x),Zag(x));
        }
        else x==ls[y]?Zig(x):Zag(x);
    }
}
void Access(int x)
{
    for(int t=0;x;x=fa[x])
    {
        Splay(x);
        rs[x]=t;
        MT(x);t=x;
    }
}
void Makeroot(int x)
{
    Access(x);
    Splay(x);
    rev[x]^=1;
}
int Findroot(int x)
{
    Access(x);
    Splay(x);
    while(ls[x])x=ls[x];
    return x;
}
void Link(int x,int y)
{
    Makeroot(x);
    fa[x]=y;
}
void Cut(int x,int y)
{
    Makeroot(x);
    Access(y);
    Splay(y);
    ls[y]=fa[x]=0;
}
void Insert(node &p,int ty)
{
    int x=p.x,y=p.y,t,d;
    if(Findroot(x)!=Findroot(y))
    {
        Link(x,p.id);
        Link(y,p.id);
        if(ty)AS[++Atop]=(node){x,p.id,1,0},AS[++Atop]=(node){y,p.id,1,0};
        ans+=p.z;
    }
    else
    {
        Makeroot(x);
        Access(y);
        Splay(y);
        if(Max[y]>p.z)
        {
            t=id[y];
            d=Hash[t];
            Cut(t,E[d].x);
            Cut(t,E[d].y);
            ans-=v[t];
            if(ty)AS[++Atop]=(node){t,E[d].x,2,0},AS[++Atop]=(node){t,E[d].y,2,0};
            Link(x,p.id);
            Link(y,p.id);
            ans+=p.z;
            if(ty)AS[++Atop]=(node){x,p.id,1,0},AS[++Atop]=(node){y,p.id,1,0};
        }
    }
}
void Recover(int T)
{
    while(Atop!=T)
    {
        if(AS[Atop].z==1)Cut(AS[Atop].x,AS[Atop].y);
        else Link(AS[Atop].x,AS[Atop].y);
        Atop--;
    }
}
void CDQ(int l,int r)
{
    int i,j,k,x,y,las=Atop;
    ll pans=ans;
    if(l==r){Ans[l]=ans;return;}
    int mid=l+r>>1;
    for(i=mid+1;i<=r;i++)if(L[i]<=l)Insert(LE[i],1);
    CDQ(l,mid);Recover(las);ans=pans;
    for(i=l;i<=mid+1;i++)if(R[i]>r)Insert(RE[i],1);
    CDQ(mid+1,r);Recover(las);ans=pans;
}
int main()
{
    int i,j,k,x,y,z;
    n=read();m=read();q=read();tot=n;
    for(i=1;i<=m;i++)
    {
        x=read();y=read();z=read();
        E[++TOT]=(node){x,y,z,0};
        la[TOT]=0;
    }
    for(i=1;i<=q;i++)
    {
        x=read();y=read();
        L[i]=la[x];la[x]=i;
        LE[i]=E[x];E[x].z=y;
        R[L[i]]=i;RE[L[i]]=LE[i];
        LE[i].id=++tot;v[tot]=LE[i].z;Hash[tot]=x;
        RE[L[i]].id=++tot;v[tot]=LE[i].z;Hash[tot]=x;
    }
    for(i=1;i<=TOT;i++)
    {
        k=la[i];
        if(k==0||k==1)E[i].id=++tot,v[tot]=E[i].z,Hash[tot]=i,Insert(E[i],0);
        else R[k]=q+1,RE[k]=E[i],RE[k].id=++tot,v[tot]=RE[k].z,Hash[tot]=i;
    }
    CDQ(1,q);
    for(i=1;i<=q;i++)printf("%lld\n",Ans[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值