省选专练HNOI2010city城市建设

妈的,我也是个睿智,当年CDQ在wc讲的时候给了辣么多例题,最后压轴题就是这个。

于是学了CDQ分治的我当天晚上强行写了这个。

题意很简单,在不改变树的状况下,强行修改边权,维护动态最小生成树。

两种做法,都不简单。

考虑对询问进行分治,假设正在处理询问区间 [l,r][l,r] 。
把所有边分为待修改(即在区间 [l,r][l,r] 中存在修改)和确定的。考虑把待修改的边权赋值为INF后来做Kruskal,那么仍不能加入最小生成树的确定边一定不能加入最小生成树,直接删除;考虑把待修改的边权赋值为-INF后来做Kruskal,那么仍能够加入最小生成树的确定边一定在最小生成树上,用并查集缩点。
接下来递归处理 [l,mid][l,mid] 和 [mid+1,r][mid+1,r] 。
可以发现这两种操作后,边数一定不会超过 (r−l+1)×2−1(r−l+1)×2−1 。
T(n)=2×T(n/2)+O(mlogm)T(n)=2×T(n/2)+O(mlog⁡m) ,解得 T(n)=O(mlognlogm)T(n)=O(mlog⁡nlog⁡m) 。

1 CDQ分治强行离线

/*
假设G是一个带权无向图的边集,S是G的一个子集。 将S中边权设为+inf后,T是图G的最小生成树。 也可以说T是G-S的MST

定理1:不管S中的边权怎么变化,G的最小生成树T’将属于T∪S。 对于任意一条不属于T∪S的边,我们可以在T中找到链接它两端的一条路径。 
由于这些边取值都和S无关,无论S权值怎么更改,这个环上这条边最大,不会进入MST 
定理2:在定理1的前提下,我们可以在不影响T’的情况下,将G的边数缩减到n+|s|-1以下。 
直接运用定理1,G-S的最小生成树最多有N-1条边。 
其他的边不可能在T’中,我们可以安全地删除掉。 
这一步被称为Reduction,效果是减少了G的边数。 
复杂度同MST是O(mlogm),m=|G|。 
定理3:不管S中的边权怎么变化。G的最小生成树T’将包含T-S。 
考虑将S的权值一条边一条边提升的情况。 
每提升一条边权值,MST要么不变,要么就是S中的一条边离开,一条新边加入。 
无论如何,T-S这些边都不会离开MST。 
定理4:在定理3的前提下,我们可以在不影响T’的情况下,将G的点数缩减到|s|+1以下。 
假设已经对图进行了Reduction。 
根据定理3,由于T-S的边不离开MST,我们可以将这些边连接的点合并为联通分量,将联通分量视为节点。 
之后根据节点归属更新边表即可。 
这一步被称为Contraction,效果是减少了G的点数。 
复杂度同MST,O(mlogm) 
根据上面的推论,我们可以得到结论: 
给定一个图G和操作序列S,可以在O(mlogm)时间内通过reduction-contraction将图的边数缩小到n+|s|-1,点数缩小到|s|+1而保持求解过程的正确性。
*/
#include<bits/stdc++.h>
using namespace std;
inline void read(int &x){
	x=0;
	int 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();
	}
	x*=f;
}
struct edge{
	int u,v,w,del,uni,num;
};
vector<edge> e;
int n,m,q;
int cnt=0;
struct Data{
	int x,w;
}que[100000];
int fa[100000]={0};
bool operator <(edge a,edge b){
	return a.w<b.w;
}
long long ans[300000]={0};
int find(int x){
	if(fa[x]==x){
		return x;
	}
	else{
		return fa[x]=find(fa[x]);
	}
}
bool union_n(int x,int y){
	int dx=find(x);
	int dy=find(y);
	if(dx==dy)
		return false;
	fa[dx]=dy;
	return true;
}
void init(int n){
	for(int i=0;i<=n;i++){
		fa[i]=i;
	}	
}
void solve(int l,int r,vector<edge> e,int n,long long sum,int newl,int newr)
{
    map<int,int> hash;
    int mid=(l+r)>>1;
    if (l==r) newr=r;
    for (int i=0;i<e.size();i++) hash.insert(make_pair(e[i].num,i));
    for (int i=newl;i<=newr;i++) //修改边操作 
    {
        Data &q=que[i];
        if (hash.count(q.x)) 
            e[hash[q.x]].w=que[i].w;
    }
    sort(e.begin(),e.end());
    if (l==r)//找到答案 
    {
        init(n+10);//为了保险多初始化一点
        for (int i=0;i<e.size();i++) if (union_n(e[i].u,e[i].v)) sum+=e[i].w;
        ans[l]=sum;
        return; 
    }
    set<int> t;//仅仅需要使用l,r之间的边 
    for (int i=l;i<=r;i++) t.insert(que[i].x);
    init(n+10);
    for (int i=0;i<e.size();i++)//缩边,将询问中的边设置为INF.有定理这时总边集e中不在生成树里的边,一定不在l,r查询的生成树中 
    {
        if (t.count(e[i].num)) continue;//这一步相当于把该条边的边权定为无穷大,相当于删掉了该边 
        if (!union_n(e[i].u,e[i].v)) e[i].del=true;
    }
    init(n+10);
    for (int i=0;i<e.size();i++)//缩点,将询问边边权设为无穷小.有定理此时总边集e中在生成树中的边必定在l,r查询的生成树中 
        if (t.count(e[i].num)) union_n(e[i].u,e[i].v);//删掉一条边合并两个点
    for (int i=0;i<e.size();i++)
    {
        if (t.count(e[i].num)||e[i].del) continue;
        if (union_n(e[i].u,e[i].v)) e[i].uni=true,sum+=e[i].w;//这条边一定在生成树中 
    }
    init(n+10);//开始重构图 
    for (int i=0;i<e.size();i++) if (e[i].uni) union_n(e[i].u,e[i].v);//缩点 
    map<int,int> mp;
    int top=0;
    vector<edge> E;
    for (int i=1;i<=n;i++) 
        if (find(i)==i) 
            mp[i]=++top;
    for (int i=0;i<e.size();i++)
    {
        if (e[i].del||e[i].uni) continue;//不考虑一定在生成树中的和一定不在生成树中的边 
        edge T=e[i];T.u=mp[find(T.u)];T.v=mp[find(T.v)];
        E.push_back(T);
    }
    solve(l,mid,E,top,sum,l,0);
    solve(mid+1,r,E,top,sum,l,mid);
}
//void solve(int l,int r,vector<edge> e,int n,long long sum,int newr,int newl){
//	map<int,int> hash;
//	int mid=(l+r)/2;
//	if(l==r)
//		newr=r;
//	for(int i=0;i<e.size();i++){
//		hash.insert(make_pair(e[i].num,i));
//	}
//	for(int i=newl;i<=newr;i++){
//		Data &q=que[i];
//		if(hash.count(q.x)){
//			e[hash[q.x]].w=que[i].w;
//		}
//	}
//	sort(e.begin(),e.end());
//	if(l==r){
//		init(n+20);
//		for(int i=0;i<e.size();i++){
//			if(union_n(e[i].u,e[i].v)){
//				sum+=e[i].w;
//			}
//		}
//		ans[l]=sum;
//		return;
//	}
//	set<int> t;
//	for(int i=l;i<=r;i++){
//		t.insert(que[i].x);
//	}
//	init(n+20);
//	for(int i=0;i<e.size();i++){
//		if(t.count(e[i].num)){
//			continue;
//		}
//		if(!union_n(e[i].u,e[i].v)){
//			e[i].del=true;
//		}
//	}
//	init(n+20);
//	for(int i=0;i<e.size();i++){
//		if(t.count(e[i].num)){
//			union_n(e[i].u,e[i].v);
//		}
//	}
//	for(int i=0;i<e.size();i++){
//		if(t.count(e[i].num)||e[i].del==true){
//			continue;
//		}
//		if(union_n(e[i].u,e[i].v)){
//			e[i].uni=true;
//			sum+=e[i].w;
//		}
//	}
//	init(n+10);
//	//重新构图了^0^
//	for(int i=0;i<e.size();i++){
//		if(e[i].uni){
//			union_n(e[i].u,e[i].v);//缩去已连接的边。 
//		}
//	} 
//	map<int,int> mp;
//	int top=0;
//	vector<edge> E;
//	for(int i=1;i<=n;i++){
//		if(find(i)==i){
//			mp[i]=++top;
//		}
//	} 
//	for(int i=0;i<e.size();i++){
//		if(e[i].del||e[i].uni)
//			continue;
//		edge T=e[i];
//		T.u=mp[find(T.u)];
//		T.v=mp[find(T.v)];
//		E.push_back(T);
//	}
//	solve(l,mid,E,top,sum,l,0);
//	solve(mid+1,r,E,top,sum,l,mid);
//}
int main(){
	read(n);
	read(m);
	read(q);
	for(int i=1;i<=m;i++){
		int u,v,w;
		read(u);
		read(v);
		read(w);
		edge T;
		T.u=u;
		T.v=v;
		T.w=w;
		T.num=i;
		T.del=0;
		T.uni=0;
		e.push_back(T);
	}
	for(int i=1;i<=q;i++){
		read(que[i].x);
		read(que[i].w);
	}
	solve(1,q,e,n,0,1,0);
	for(int i=1;i<=q;i++){
		printf("%lld\n",ans[i]);
	}
} 

2 LCT在线做CDQ

/*LCT*/
#include <bits/stdc++.h>
#define il inline
#define RG register
#define ll long long
#define N (150005)
#define ls (x<<1)
#define rs (x<<1|1)
#define max(a,b) (a>b ? a : b)
#define swap(a,b) (tmp=a,a=b,b=tmp)
#define isroot(x) (ch[fa[x]][0]!=x && ch[fa[x]][1]!=x)
#define makeroot(x) (access(x),splay(x),rev[x]^=1)
#define link(x,y) (makeroot(x),fa[x]=y)
#define cut(x,y) (makeroot(x),access(y),splay(y),ch[y][0]=fa[x]=0,pushup(y))
#define pushdown(x) (rev[x]=0,rev[ch[x][0]]^=1,rev[ch[x][1]]^=1,swap(ch[x][0],ch[x][1]))
#define insert(from,to) (g[++num]=(E){head[from],to},head[from]=num)

using namespace std;

struct data{ int mx,id; }tr[N],pt[N];
struct edge{ int u,v,w,l,r; }e[N],q[N];
struct E{ int nt,to; }g[20*N];

int ch[N][2],fa[N],id[N],mx[N],val[N],rev[N],st[N],Q,n,m;
int head[N],que[N],b[N],t[N],f[N],sz[N],num,tot,end,tmp;
ll ans[N],Ans;

il int gi(){
  RG int x=0,q=1; RG char ch=getchar();
  while ((ch<'0' || ch>'9') && ch!='-') ch=getchar();
  if (ch=='-') q=-1,ch=getchar();
  while (ch>='0' && ch<='9') x=x*10+ch-48,ch=getchar();
  return q*x;
}

il void pushup(RG int x){
  RG int &l=ch[x][0],&r=ch[x][1]; mx[x]=val[x],id[x]=x;
  mx[x]<mx[l]?(mx[x]=mx[l],id[x]=id[l]):0;
  mx[x]<mx[r]?(mx[x]=mx[r],id[x]=id[r]):0; return;
}

il void rotate(RG int x){
  RG int y=fa[x],z=fa[y],k=ch[y][0]==x,&v=ch[x][k];
  !isroot(y)?(ch[z][ch[z][1]==y]=x):0;
  fa[x]=z,ch[y][k^1]=v,fa[v]=y;
  v=y,fa[y]=x,pushup(y),pushup(x); return;
}

il void splay(RG int x){
  RG int top=0; st[++top]=x;
  for (RG int i=x;!isroot(i);i=fa[i]) st[++top]=fa[i];
  for (RG int i=top;i;--i) if (rev[st[i]]) pushdown(st[i]);
  while (!isroot(x)){
    RG int y=fa[x],z=fa[y];
    if (!isroot(y)) rotate((ch[z][0]==y)^(ch[y][0]==x)?x:y);
    rotate(x);
  }
  return;
}

il void access(RG int x){
  RG int t=0;
  while (x) splay(x),ch[x][1]=t,pushup(x),t=x,x=fa[x];
  return;
}

il int find(RG int x){ while (x!=f[x]) x=f[x]; return x; }

il data query(RG int x,RG int y){
  makeroot(x),access(y),splay(y);
  return (data){mx[y],id[y]};
}

il void query(RG int x,RG int l,RG int r,RG int k){
  if (q[k].l<=l && r<=q[k].r){ insert(x,k); return; }
  RG int mid=(l+r)>>1;
  if (q[k].r<=mid) query(ls,l,mid,k);
  else if (q[k].l>mid) query(rs,mid+1,r,k);
  else query(ls,l,mid,k),query(rs,mid+1,r,k);
  return;
}

il void add(RG int x){
  for (RG int i=head[x],k,x,y;i;i=g[i].nt){
    k=g[i].to;
    if ((x=find(q[k].u))!=(y=find(q[k].v))){
      Ans+=q[k].w,link(q[k].v,k+n),link(k+n,q[k].u);
      if (sz[x]>sz[y]) swap(x,y); sz[y]+=sz[x],f[x]=y;
      que[++end]=k,b[end]=0,pt[end]=(data){x,y};
    }else{
      RG data res=query(q[k].v,q[k].u);
      if (res.mx<=q[k].w) continue; res.id-=n;
      cut(q[res.id].u,res.id+n),cut(res.id+n,q[res.id].v);
      link(q[k].u,k+n),link(k+n,q[k].v);
      Ans+=q[k].w-res.mx,que[++end]=k,b[end]=res.id;
    }
  }
  return;
}

il void del(RG int top){
  RG int id,x,y;
  while (end>top){
    id=que[end],Ans-=q[id].w,cut(q[id].u,id+n),cut(id+n,q[id].v);
    if (!b[end]) x=pt[end].mx,y=pt[end].id,sz[y]-=sz[x],f[x]=x;
    else id=b[end],Ans+=q[id].w,link(q[id].u,id+n),link(id+n,q[id].v);
    --end;
  }
  return;
}

il void solve(RG int x,RG int l,RG int r){
  RG int tp=end; add(x);
  if (l==r) ans[l]=Ans; else{
    RG int mid=(l+r)>>1;
    solve(ls,l,mid),solve(rs,mid+1,r);
  }
  del(tp); return;
}

int main(){
#ifndef ONLINE_JUDGE
  freopen("city.in","r",stdin);
  freopen("city.out","w",stdout);
#endif
  n=gi(),m=gi(),Q=gi();
  for (RG int i=1;i<=n;++i) f[i]=i,sz[i]=1;
  for (RG int i=1;i<=m;++i)
    e[i].u=gi(),e[i].v=gi(),e[i].w=gi(),t[i]=1;
  for (RG int i=1,k,w;i<=Q;++i){
    k=gi(),w=gi(),q[++tot]=e[k],e[k].w=w;
    q[tot].l=t[k],q[tot].r=i-1,t[k]=i;
  }
  for (RG int i=1;i<=m;++i)
    q[++tot]=e[i],q[tot].l=t[i],q[tot].r=Q;
  for (RG int i=1;i<=tot;++i) val[i+n]=q[i].w;
  for (RG int i=1;i<=tot;++i)
    if (q[i].l<=q[i].r) query(1,1,Q,i);
  solve(1,1,Q);
  for (RG int i=1;i<=Q;++i) printf("%lld\n",ans[i]);
  return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值