洛谷P3206 [HNOI2010]城市建设

神仙题

题目大意:

有一张(n)个点(m)条边的无向联通图,每次修改一条边的边权,问每次修改之后这张图的最小生成树权值和

话说是不是(cdq)题目都可以用什么数据结构莽过去啊……

这道题目确实是(cdq)好题

(cdq)分治一定要处理多维偏序问题吗?并不是,它的核心思想是一个用子问题计算对另一个子问题的贡献

我们将这道题按照时间轴来分治,那么显然一个子问题对另一个子问题是存在贡献的

我们将整张图上的边进行分类:

在当前分治区间内涉及到修改的边称为动态边

其他边称为静态边

我们在分治过程中,动态边显然会随着区间的缩小而减少,那么我们怎么来处理静态边呢?

仔细考虑,我们根本不可以发现,静态边可以分为三类:

(1.)无用边:肯定不会作为最小生成树中的边

对于这类边,我们可以直接扔掉

(2.)必要边:必作为生成树中的边

对于这种边,我们可以直接把边权计入答案,然后把边连接的两个点用并查集合并到一起

(3.)其他边

没什么办法了,直接传给子区间吧

按照这种方法,每分治到一个更小的区间,其他区间的动态边就会变成静态边,当我们分治到(l==r)时,所有的边都会变为静态边,那么其他边将不存在,必要边贡献的答案就是答案了

现在我们有两个问题:

1:如何求出前两类边?

求无用边,我们可以将所有动态边的边权设置为(inf),然后跑(Kruskal),最后没有被用到的静态边都是无用边

求必要边,我们可以将所有动态边的边权设置为(-inf),还跑(Kruskal),最后被用到的静态边都是必要边

正确性好像挺显然的(雾

2:时间复杂度?

假设当前区间内有(k)条动态边,那么除去必要边和无用边之后,最多留下(k+1)个点和(2k)条边

由于(k)每次降低一倍,所以边数和点数都是都每次减少接近一倍,复杂度约为(O(nlog^2n))

注意我们每次操作要先求必要边再求无用边复杂度才对!!!

我们注意这道题,它的其他子问题对当前子问题的贡献并不是按照一般(cdq)的方法,而是在进入当前区间之前已经将贡献计算好了

#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define mid ((l+r)>>1)
#define lowbit(x) ((x)&(-x))
	inline int read()
	{
		int x=0;char ch,f=1;
		for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
		if(ch=='-') f=0,ch=getchar();
		while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
		return f?x:-x;
	}
	const int N=1e5+10;
	int n,m,ask;
	struct edge
	{
		int x,y,val,opt;
		inline bool operator < (const edge &t) const
		{
			return val<t.val;
		}
	}a[N];
	struct moved
	{
		int x,y;
	};
	struct node
	{
		int num,val,ret;
	}q[N];
	struct dsu
	{
		int f[N],str[N];
		struct blue {int x,y;};
		stack<blue> st;
		inline void init()
		{
			for(int i=1;i<=n;++i) f[i]=i,str[i]=1;
		}
		inline int find(int k)
		{
			return f[k]==k?k:find(f[k]);
		}
		inline void merge(int x,int y)
		{
			int tx=find(x),ty=find(y);
			if(tx==ty) return;
			if(str[tx]<str[ty]) swap(tx,ty);
			str[tx]+=str[ty];
			f[ty]=tx;
			blue tmp;
			tmp.x=tx,tmp.y=ty;
			st.push(tmp);
		}
		inline void del()
		{
			blue now=st.top();
			st.pop();
			f[now.y]=now.y;
			str[now.x]-=str[now.y];
		}
		inline void clear(int bot)//可撤销并查集
		{
			while(st.size()>bot) del();
		}
	}s1,s;//s全局
	int ret[30];
	//reduction无用边	
	//contraction必须边
	vector<edge> eg[30],tr;//存每一层静态边和临时静态边
	vector<moved> rose;//存当前动态边
	int bot[30];
	bool book[N];
	inline void push_down(int dep)
	{
		tr.clear();
		for(int i=0;i<eg[dep].size();++i) tr.push_back(eg[dep][i]);
		sort(tr.begin(),tr.end());
		s1.clear(0);
		ret[dep+1]=ret[dep];
		for(int i=0;i<rose.size();++i) s1.merge(rose[i].x,rose[i].y);
		rose.clear();
		for(int tx,ty,i=0;i<tr.size();++i)//求必要边
		{
			tx=s1.find(tr[i].x),ty=s1.find(tr[i].y);
			if(tx==ty) continue;
			tr[i].opt=1;
			s1.merge(tr[i].x,tr[i].y);
			s.merge(tr[i].x,tr[i].y);
			ret[dep+1]+=tr[i].val;
		}
		s1.clear(0);//撤销
		for(int i=0;i<tr.size();++i)//求无用边
		{
			if(tr[i].opt) continue;
			if(s.find(tr[i].x)==s.find(tr[i].y)||s1.find(tr[i].x)==s1.find(tr[i].y))
			{
				tr[i].opt=-1;
				continue;
			}
			s1.merge(tr[i].x,tr[i].y);
		}
		s1.clear(0);//撤销
		eg[dep+1].clear();
		for(int tx,ty,i=0;i<tr.size();++i)
		{
			if(tr[i].opt) continue;
			tx=s.find(tr[i].x),ty=s.find(tr[i].y);
			if(tx==ty) continue;
			edge tmp;
			tmp.x=tx,tmp.y=ty,tmp.val=tr[i].val,tmp.opt=0;
			eg[dep+1].push_back(tmp);
		}
		return;
	}
	inline void cdq(int l,int r,int dep)
	{
		bot[dep]=s.st.size();
		if(l==r)
		{
			edge tmp;
			tmp.x=s.find(a[q[r].num].x),tmp.y=s.find(a[q[r].num].y);//底层
			tmp.val=q[r].val,tmp.opt=0;
			a[q[r].num].val=q[r].val;//注意这里,直接更改
			eg[dep].push_back(tmp);
			push_down(dep);
			q[r].ret=ret[dep+1];
			s.clear(bot[dep-1]);
			return;
		}
		for(int i=l;i<=mid;++i) book[q[i].num]=1;//递归左边时,右边的动态变静态
		for(int i=mid+1;i<=r;++i)
		{
			if(book[q[i].num]) continue;
			edge tmp;
			tmp.x=s.find(a[q[i].num].x),tmp.y=s.find(a[q[i].num].y);
			tmp.val=a[q[i].num].val,tmp.opt=0;
			eg[dep].push_back(tmp);
		}
		for(int i=l;i<=mid;++i)//左边的动态存进去
		{
			moved tmp;
			tmp.x=s.find(a[q[i].num].x),tmp.y=s.find(a[q[i].num].y);
			rose.push_back(tmp);
		}
		
		push_down(dep);//下放
		
		for(int i=mid+1;i<=r;++i)
		{
			if(book[q[i].num]) continue;
			eg[dep].pop_back();
		}
		for(int i=l;i<=mid;++i) book[q[i].num]=0;//回溯
		
		cdq(l,mid,dep+1);
		
		for(int i=0;i<eg[dep].size();++i) eg[dep][i].opt=0;//去掉标记
		
		for(int i=mid+1;i<=r;++i) book[q[i].num]=1;
		
		for(int i=l;i<=mid;++i)//左边的动态变静态
		{
			if(book[q[i].num]) continue;
			edge tmp;
			tmp.x=s.find(a[q[i].num].x),tmp.y=s.find(a[q[i].num].y);
			tmp.val=a[q[i].num].val,tmp.opt=0;
			eg[dep].push_back(tmp);
		}
		for(int i=mid+1;i<=r;++i)//右边的动态放进去
		{
			book[q[i].num]=0;
			moved tmp;
			tmp.x=s.find(a[q[i].num].x),tmp.y=s.find(a[q[i].num].y);
			rose.push_back(tmp);
		}
		push_down(dep);
		cdq(mid+1,r,dep+1);
		s.clear(bot[dep-1]);//撤销并查集
	}
	inline void main()
	{
		n=read(),m=read(),ask=read();
		s1.init(),s.init();
		for(int i=1;i<=m;++i) a[i].x=read(),a[i].y=read(),a[i].val=read();
		for(int i=1;i<=ask;++i)
		{
			q[i].num=read(),q[i].val=read();
			book[q[i].num]=1;
		}
		for(int i=1;i<=m;++i)
		{
			if(book[i]) continue;
			eg[1].push_back(a[i]);
		}
		for(int i=1;i<=ask;++i) book[q[i].num]=0;
		cdq(1,ask,1);
		for(int i=1;i<=ask;++i) printf("%lld
",q[i].ret);
	}
}
signed main()
{
	red::main();
	return 0;
}
原文地址:https://www.cnblogs.com/knife-rose/p/12045917.html