欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

题解 | Graph Games-2019牛客暑期多校训练营第三场A题

程序员文章站 2022-04-01 13:03:17
...

题目来源于牛客竞赛:https://ac.nowcoder.com/acm/contest/discuss
题目描述:
You are given an undirected graph with N vertices and M edges. The edges are numbered from 1 to M. Denote the set S(x) as: All the vertices we can reach from vertex x by exactly one edge.

You are supposed to deal with Q operations of the following two types: (1,l,r)-- reverse the status of edges numbered between l to r(1≤l≤r≤M). i e.Delete the edge if it is in the graph, otherwise add it to the graph.

(2,u,v) – ask whether S(u) and S(v) are exactly the same (1≤u≤v≤N
).

Note that all the M edges are in the graph at the beginning.

输入描述:
The input contains multiple cases. The first line of the input contains a single positive integer T, the number of cases.

For each case, the first line contains two integers N (1≤N≤100000) and M (1≤M≤200000), the number of vertices and edges in the graph. In the following M lines, the i-th line contains two integers ui,vi(1≤ui,vi≤N), describing the the i-th edge (ui,vi). Each edge appears in the input at most once. The (M+2)-th line contains a integer Q(1≤Q≤200000) , the number of operations. In the following Q lines, each line contains three integers, describing an operation.

The total sum of N over all cases does not exceed 150000. The total sum of \ M Mover all cases does not exceed 800000. The total sum of Q over all cases does not exceed 600000.

输出描述:
For each case, print a string in a line. The length of the string should equal the number of operations of type 2. If the answer is yes, the i-th character of the string should be ‘1’, otherwise it should be ‘0’. Check the samples for more details.

示例1:
输入
1
5 4
1 2
1 3
4 2
4 3
3
2 1 4
1 1 1
2 1 2

输出
10

题解:
显然,我们要用hash等技巧来显式地“表现” S(x)
用到一个技巧:给每一个点一个 long long 范围内的随机数key , 把一个集合S(x)的值定义为其里面的点key的异或值,那么判集合相等就可以用判数值相等来替代。

然后考虑如何维护这些异或值
有一个比较显然的 Q√m log M的做法
我们按点(在原图)的度数分块。对于所有“小点”,每次我们暴力枚举与它相邻的边,看看是否存在,存在的话,用另一侧的点更新它的xor值。至于判断如何存在,其实是一个区间异或、单点查询的问题,我们可以用树状数组做到logM的快速查询。

对于所有“大点”,总数不会超过√M。 我们在每次修改的时候,暴力维护每一次大点的答案即可。这里提供一种可能的方法:对每一个“大点”,维护一个它的边的vector(按标号排序)。 那么每次一个操作对其的贡献的vector里连续一段。我们只要把这连续的一段的xor值给异或掉即可。这样可能需要一个logM的定位。

数据没有特意卡带log的复杂度。
其实优化一下,就能得到一个√M的做法了。
• 对于“小点”,瓶颈是维护“区间异或,单点查询”。注意到修改只有Q个,但询问有 Q√M 个。所以我们可以匀一匀复杂度,改成对边的序列分块。每次修改时,块间打标 记,块内暴力;询问时可以直接O(1)得出结果。
• 对于“大点”,我们还是在每次修改时暴力维护。考虑一开始对边序列分块,那么一个 块最多只会对2*√M个点产生贡献。我们记录一下每一个块对它能影响的每一个大点的 xor值的贡献,以及如果翻转了这个块后的贡献。每次修改时,在块间打翻转标记,两 侧块暴力重构对大点的贡献。每次询问一个大点时,我们枚举所有的块,把所有的块对 它的贡献都异或起来即可。
• 综上,总复杂度是O(Q√M),内存O(M),支持在线。

代码:

#include<bits/stdc++.h>
#define N 100005
#define M 200005
#define U 1005
using namespace std;
typedef long long LL;
int Go[M<<1],Next[M<<1],Value[M<<1],End[N],cnt;
int be[M],tag[M],u[M],v[M],id[M],val[M];
LL F[U][U],G[U][U],key[N];
int st[U],ed[U],d[N],Q[N],n,m,S,tot;
void add(int u,int v,int w){
	Go[++cnt]=v;Value[cnt]=w;
	Next[cnt]=End[u];End[u]=cnt;
}
void Force(int l,int r){
	int k=be[l];
	for (int i=l;i<=r;i++){
		val[i]^=1;
		if (d[u[i]]>S){
			F[k][id[u[i]]]^=key[v[i]];
			G[k][id[u[i]]]^=key[v[i]];
		}
		if (d[v[i]]>S){
			F[k][id[v[i]]]^=key[u[i]];
			G[k][id[v[i]]]^=key[u[i]];
		}
	}
}
void Update(int l,int r){
	int u=be[l],v=be[r];
	if (u==v) return Force(l,r);
	for (int i=u+1;i<v;i++) tag[i]^=1;
	Force(l,ed[u]);Force(st[v],r);
}
LL Query(int x){
	LL ret=0;
	if (d[x]<=S){
		for (int i=End[x];i;i=Next[i])
			if (val[Value[i]]^tag[be[Value[i]]])
				ret^=key[Go[i]];
	}
	else {
		for (int i=1;i<=tot;i++)
			ret^=tag[i]?G[i][id[x]]:F[i][id[x]];
	}
	//printf("%d %lld\n",x,ret);
	return ret;
}
int Judge_Linux(){
	for (int i=1;i<=10;i++)
		if (rand()>32767) return 1;
	return 0;
}
void read(int &x){
	char ch = getchar();x = 0;
	for (; ch < '0' || ch > '9'; ch = getchar());
	for (; ch >='0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
}
LL Rand1(){return ((LL)rand()<<30LL)+rand();}
LL Rand2(){return ((LL)rand()<<45LL)+((LL)rand()<<30LL)+((LL)rand()<<15LL)+rand();}
int main(){
	int Linux=Judge_Linux();
	int sumn=0,summ=0,sumq=0,T;
	scanf("%d",&T);
	while (T--){
		scanf("%d%d",&n,&m);
		for (int i=1;i<=n;i++) End[i]=d[i]=0;cnt=0;
		for (int i=1;i<=n;i++) key[i]=Linux?Rand1():Rand2();
		for (int i=1;i<=m;i++){
			read(u[i]);read(v[i]);
			d[u[i]]++,d[v[i]]++;
			add(u[i],v[i],i);add(v[i],u[i],i);
		}
		for (S=1;S*S<=m;S++);--S;tot=0;
		for (int i=1;i<=m;i+=S){
			tag[++tot]=0;st[tot]=i;ed[tot]=min(i+S-1,m);
			for (int j=st[tot];j<=ed[tot];j++) be[j]=tot;
		}
		for (int i=1;i<=m;i++) val[i]=1;
		*Q=0;for (int i=1;i<=n;i++)
			if (d[i]>S) Q[++*Q]=i,id[i]=*Q;
		for (int i=1;i<=*Q;i++){
			for (int j=1;j<=tot;j++) F[j][i]=G[j][i]=0;
			for (int t=End[Q[i]];t;t=Next[t])
				F[be[Value[t]]][i]^=key[Go[t]];
		}
		int T;read(T);
		sumn+=n;summ+=m;sumq+=T;
		while (T--){
			int opt,l,r;read(opt);read(l);read(r);
			if (opt==1) Update(l,r);
				   else putchar((Query(l)==Query(r))+'0');
			//if ((T%100==0)) fprintf(stderr,"%d\n",T);
		}
		puts("");
	}
	fprintf(stderr,"%d %d %d\n",sumn,summ,sumq);
}

更多问题,更详细题解可关注牛客竞赛区,一个刷题、比赛、分享的社区。
传送门:https://ac.nowcoder.com/acm/contest/discuss