IOI2021集训队作业277BK Tours

给出一个无向图,你需要给每条边染\(k\)种色,使得:对于每个环,这\(k\)种色的出现次数相同。

问有哪些合法的\(k\)

\(n,m\le 2000\)


假如环不相交,那么可以将每个环上的边分成一个集合。把集合大小求\(\gcd\)即可。

当两个环相交的时候,发现原本的两个集合\(S_1,S_2\)要分成三类:\(S_1\bigcap S_2,S_1\setminus S_1\bigcap S_2,S_2\setminus S_1\bigcap S_2\)

推广到更多的环,可以发现:两条边在同一个集合,当且仅当包含它们的环的集合相同。

问题变为求出所有的集合(注意忽略桥边)。

到了这里我卡住了。%%%ymq秒掉。

判断两条边\(a,b\)是否在同一集合(称其为等价类)。先删\(a\)看看\(b\)是否在环中,再删\(b\)看看\(a\)是否在环中。如果都不在,那么它们为等价类。时间\(O(m^2)\)

还有更妙的做法:建dfs树,对于非树边随机赋一个权值,将其跨过的树边的权值都异或上这个权值。最终权值相同的为等价类。

简要说明正确性:首先两条非树边不可能为等价类,因为非树边可以和树边组成环;其次不需要考虑两条或以上非树边所组成的环,因为这个环一定可以拆成多个只有一条非树边的环,并且每个环覆盖的树边为这个环覆盖的树边的子集。

时间\(O(m+m\lg m)\)(后面这个\(\lg\)map


using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 2005
int gcd(int a,int b){
	int k;
	while (b)
		k=a%b,a=b,b=k;
	return a;
}
int n,m;
struct EDGE{
	int to;
	EDGE *las;
} e[N*2];
int ne;
EDGE *last[N];
void link(int u,int v){
	e[ne]={v,last[u]};
	last[u]=e+ne++;
}
int era;
int bz[N][N];
int dfn[N],low[N],cnt;
void dfs(int x,int fa,int bz[]){
	low[x]=dfn[x]=++cnt;
	for (EDGE *ei=last[x];ei;ei=ei->las){
		if (ei-e>>1==era || ei->to==fa)
			continue;
		if (!dfn[ei->to]){
			dfs(ei->to,x,bz);
			low[x]=min(low[x],low[ei->to]);
			if (low[ei->to]<=dfn[x])
				bz[ei-e>>1]=1;
		}
		else{
			low[x]=min(low[x],dfn[ei->to]);
			bz[ei-e>>1]=1;
		}
	}
}
int dsu[N],c[N];
int getdsu(int x){return dsu[x]==x?x:dsu[x]=getdsu(dsu[x]);}
void work(int x){
	era=x;
	memset(dfn,0,sizeof(int)*(n+1));
	cnt=0;
	for (int i=1;i<=n;++i)
		if (!dfn[i])
			dfs(i,0,bz[x]);
}
int main(){
	freopen("in.txt","r",stdin);
	freopen("right.txt","w",stdout);
	scanf("%d%d",&n,&m);
	for (int i=0;i<m;++i){
		int u,v;
		scanf("%d%d",&u,&v);
		link(u,v),link(v,u);
	}
	work(m);
	for (int i=0;i<m;++i)
		if (bz[m][i])
			work(i);
//	for (int i=0;i<=m;++i,printf("\n"))
//		for (int j=0;j<m;++j)
//			printf("%d ",bz[i][j]);
	for (int i=0;i<m;++i)
		dsu[i]=i;
	for (int i=0;i<m;++i)
		if (bz[m][i])
			for (int j=0;j<m;++j)
				if (bz[m][j] && bz[i][j]==0 && bz[j][i]==0)
					dsu[getdsu(i)]=getdsu(j);
	for (int i=0;i<m;++i)
		if (bz[m][i])
			c[getdsu(i)]++;
	int g=0;
	for (int i=0;i<m;++i)
		if (bz[m][i])
			g=gcd(g,c[i]);
	printf("1");
	for (int i=2;i<=m;++i)
		if (g%i==0)
			printf(" %d",i);
	return 0;
}

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <ctime>
#include <map>
#define N 2005
#define ll long long
#define irand (rand()*RAND_MAX+rand())
#define lrand ((ll)irand*RAND_MAX*RAND_MAX+irand)
int gcd(int a,int b){
	int k;
	while (b)
		k=a%b,a=b,b=k;
	return a;
}
int n,m;
struct EDGE{
	int to;
	EDGE *las;
} e[N*2];
int ne;
EDGE *last[N];
void link(int u,int v){
	e[ne]={v,last[u]};
	last[u]=e+ne++;
}
ll v[N],s[N];
map<ll,int> ma;
int vis[N],ins[N];
void dfs(int x,int fa=0){
	vis[x]=1;
	ins[x]=1;
	for (EDGE *ei=last[x];ei;ei=ei->las){
		if (ei->to==fa) continue;
		if (!vis[ei->to]){
			dfs(ei->to,x);
			s[x]^=v[ei-e>>1]=s[ei->to];
		}
		else if (ins[ei->to]){
			s[ei->to]^=v[ei-e>>1]=lrand;
			s[x]^=v[ei-e>>1];
		}
	}
	ins[x]=0;
}
int main(){
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
	srand(time(0));
	scanf("%d%d",&n,&m);
	for (int i=1;i<=m;++i){
		int u,v;
		scanf("%d%d",&u,&v);
		link(u,v),link(v,u);
	}
	for (int i=1;i<=n;++i)
		if (!vis[i])
			dfs(i);
	for (int i=0;i<m;++i)
		if (v[i])
			ma[v[i]]++;
	int g=0;
	for (auto p=ma.begin();p!=ma.end();++p)
		g=gcd(g,p->second);
	printf("1");
	for (int i=2;i<=m;++i)
		if (g%i==0)
			printf(" %d",i);
	return 0;
}

posted @ 2020-11-26 15:12  jz_597  阅读(157)  评论(0编辑  收藏  举报