BZOJ 3562 SHOI2014 神奇化合物

Problem

BZOJ
题目大意就是说要求动态查询图中联通块的数量。

Solution

首先对于联通块数量,我们可以用dfs,在 O ( n ) O(n) O(n)的时间内求出来。对于始终没有被删除的边,对答案不会产生任何影响,为了减轻计算量,可以直接进行缩点,因此要进行离线处理。
其次,在后来的连接与断开操作中,我们只需要知道,是否连接了两个本不联通的块或者是否切断了本联通的块即可更新答案。但是对于图可能要切断多条边才能使其不联通,那么我们可以设一个can数组记录可以保证联通的边有多少条。然后直接用并查集进行维护即可。


然后这种做法应该是可以出数据卡的。所以zyf大佬就讲了一种时间线段树的做法。
感谢zyf大佬的耐心讲解qwqqq

同样的,也是离线处理,当然也可以继续沿用楼上合并不会被删除的边进行优化。那么我们可以预处理出所有的边出现时间与终止时间。会用到一个类似于整体二分的思想。处理答案的时候,递归求解,不断的去逼近时间。我们可以利用线段树的思想,将一条边的存在时间成段地拆开。例如某条连接(x,y)的边在3时被添加,6时被删除,即它的存活时间为[3,6],而全局时间为[1,10],那么按照线段树递归地构造 (没图,要不大家手动画下线段树吧??),它就会出现在表示区间[3,3],[4,5],[6,6]三个节点中。
为了方便地预处理,我们就调用add_tree函数,塞进bel数组里去即可,当然我们在撤销合并操作的时候会需要一个对应的use数组来记录是否合并。在dfs的时候,就模拟加入当前边,撤销边即可。
时间复杂度: O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n)


2018.10.3 再更新一个lct的做法
离线询问,按时间排序。每次把出现时间小于等于当前时间的边加入,维护按消失时间的最大生成树,如果lct中不连通或者链上最小的消失时间小于当前询问时间就不联通。时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

Code

法一

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=5010,maxm=400010;
struct data{
	int v,nxt;
}edge[maxm];
int n,m,q,p,ans,dfn,f[maxn],vis[maxn],head[maxn],k[maxm][2],que[maxn<<1][3];
short can[maxn][maxn];
char op[5];
bool lkan[maxn],kan[maxn][maxn];
template <typename Tp> inline void read(Tp &x)
{
	x=0;char ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
inline void insert(int u,int v)
{
	edge[++p]=(data){v,head[u]};head[u]=p;
	edge[++p]=(data){u,head[v]};head[v]=p;
}
inline int find(int x){return x==f[x]?x:f[x]=find(f[x]);}
int merge(int x,int y)
{
	int fx=find(x),fy=find(y);
	if(fx==fy) return 0;
	f[fy]=fx;
	return 1;
}
int dfs(int now,int r)
{
	if(now==r) return 1;
	for(int i=head[now];i;i=edge[i].nxt)
	  if(vis[edge[i].v]!=dfn&&can[edge[i].v][now])
	  {
	  	vis[edge[i].v]=dfn;
	  	if(dfs(edge[i].v,r)) return 1;
	  }
	return 0;
}
void input()
{
	read(n);read(m);
	for(int i=1;i<=n;i++) f[i]=i;
	for(int i=1;i<=m;i++) read(k[i][0]),read(k[i][1]),merge(k[i][0],k[i][1]);
	read(q);
}
int main()
{
	int x,y;
	input();
	for(int i=1;i<=n;i++)
	{
		x=find(i);
		if(!lkan[x]){lkan[x]=1;ans++;}
	}
	for(int i=1;i<=n;i++) f[i]=i;
	for(int i=1;i<=q;i++)
	{
		scanf("%s",op);
		if(op[0]=='Q') que[i][0]=-1;
		else if(op[0]=='D')
		{
			read(x);read(y);
			que[i][0]=1;que[i][1]=x;que[i][2]=y;
			kan[x][y]=kan[y][x]=1;
		}
		else
		{
			read(x);read(y);
			que[i][0]=2;que[i][1]=x;que[i][2]=y;
		}
	}
	for(int i=1;i<=m;i++)
	  if(!kan[k[i][0]][k[i][1]]) merge(k[i][0],k[i][1]);
	for(int i=1;i<=m;i++)
	{
		x=find(k[i][0]);y=find(k[i][1]);
		can[x][y]++;can[y][x]++;
		if(can[x][y]==1) insert(find(x),find(y));
	}
	for(int i=1;i<=q;i++)
	{
		x=find(que[i][1]);y=find(que[i][2]);
		if(que[i][0]==-1) printf("%d\n",ans);
		else if(que[i][0]==1)
		{
			can[x][y]--;can[y][x]--;
			vis[x]=++dfn;//时间戳优化
			if(!dfs(x,y)) ans++;
		}
		else
		{
			if(x==y) continue;
			vis[x]=++dfn;
			if(!dfs(x,y)) ans--;
			can[x][y]++;can[y][x]++;
			if(can[x][y]==1) insert(x,y);
		}
	}
	return 0;
}

法二

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <vector>
#include <set>
using namespace std;
typedef pair<int,int> pii;
const int maxn=5010,maxm=200010,INF=0x3f3f3f3f;
struct data{
	int u,v,dec;
	bool operator < (const data &x)const
	{
		if(u==x.u)
		  return v==x.v?dec<x.dec:v<x.v;
		return u<x.u;
	}
}edge[maxm],qry[50010];
vector<int> tms[50010];
int sta[50010],end[50010],fr[50010],to[50010];
int n,m,q,num=1,nm,f[maxn],sz[maxn];
multiset<data> s;
int ans[40010];
typedef multiset<data>::iterator itr;
vector<pii> bel[160010];
vector<int> use[160010];
int kuai[160010];
char op[5];

template <typename Tp> inline void read(Tp &x)
{
	x=0;char ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}

void match(int x,int y,int now)
{
	itr it=s.lower_bound((data){x,y,-INF});
	if(it==s.end()) return ;
	if(it->u==x&&it->v==y)
	{
		sta[++nm]=it->dec;end[nm]=now;
		fr[nm]=x;to[nm]=y;
		s.erase(it);
	}
}
void input()
{
	int x,y;
	read(n);read(m);
	for(int i=1;i<=m;i++)
	{
		read(x);read(y);
		if(x>y) swap(x,y);
		edge[i]=(data){x,y,0};
		s.insert((data){x,y,1});
	}
	read(q);
	for(int i=1;i<=q;i++)
	{
		scanf("%s",op);
		if(op[0]=='D')
		{
			read(x);read(y);
			if(x>y) swap(x,y);
			match(x,y,num++);
		}
		else if(op[0]=='A')
		{
			read(x);read(y);
			if(x>y) swap(x,y);
			s.insert((data){x,y,++num});
		}
		else tms[num].push_back(i);
	}
}
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
int merge(int x,int y)
{
	int fx=find(x),fy=find(y);
	if(fx==fy) return 0;
	f[fy]=fx;
	return 1;
}
int found(int x)//因为需要撤销合并操作,不能路径压缩
{
	while(f[x]!=x) x=f[x];
	return x;
}
void add_tree(int tl,int tr,int deal,int now)
{
	if(tl>end[deal]||tr<sta[deal]) return ;
	if(tl>=sta[deal]&&tr<=end[deal])
	{
		bel[now].push_back(make_pair(fr[deal],to[deal]));
		use[now].push_back(0);
		return ;
	}
	int m=(tl+tr)>>1;
	add_tree(tl,m,deal,now<<1);
	add_tree(m+1,tr,deal,now<<1|1);
}
void input_edge(int now)
{
	if(now^1) kuai[now]=kuai[now>>1];
	for(int i=0;i<bel[now].size();i++)
	{
		pii &k=bel[now][i];
		k.first=found(k.first);k.second=found(k.second);
		if(k.first==k.second) continue;
		use[now][i]=1;
		if(sz[k.first]<sz[k.second])//启发式合并
		  f[k.first]=k.second,sz[k.second]+=sz[k.first];
		else
		  f[k.second]=k.first,sz[k.first]+=sz[k.second];
		kuai[now]--;
	}
}
void output_edge(int now)
{
	for(int i=bel[now].size()-1;i>=0;i--)
	{
		pii &k=bel[now][i];
		if(!use[now][i]) continue;
		if(sz[k.first]<sz[k.second])//撤销合并操作
		  f[k.first]=k.first,sz[k.second]-=sz[k.first];
		else
		  f[k.second]=k.second,sz[k.first]-=sz[k.second];
	}
}
void dfs(int now,int l,int r)
{
	input_edge(now);
	if(l==r)
	{
		for(int i=0;i<tms[l].size();i++)
		  ans[tms[l][i]]=kuai[now];
	}
	else
	{
		int m=(l+r)>>1;
		dfs(now<<1,l,m);
		dfs(now<<1|1,m+1,r);
	}
	output_edge(now);
}
int main()
{
	input();
	for(int i=1;i<=n;i++) f[i]=i;
	for(itr it=s.begin();it!=s.end();it++)
	{
		if(it->dec==1) merge(it->u,it->v);//将不会删除的边先merge了
		else sta[++nm]=it->dec,end[nm]=num,fr[nm]=it->u,to[nm]=it->v;
	}
	for(int i=1;i<=n;i++) sz[found(i)]++;
	for(int i=1;i<=n;i++) if(f[i]==i) kuai[1]++;//初始化答案
	for(int i=1;i<=nm;i++) add_tree(1,num,i,1);
	dfs(1,1,num);
	for(int i=1;i<=q;i++) if(ans[i]) printf("%d\n",ans[i]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值