UOJ632. 【UR #21】挑战最大团

优美的图定义为:对于任意存在边((a,b),(b,c),(c,d))的四个点,((a,c),(b,d),(a,d))不能同时不出现。

现在给出一个优美的图,要对(kin [1,n])算大小为(k)的团的个数。

(nle 8000)


结论1:优美的图的子图一定是优美的。(显然)

结论2:优美的图的补图一定是优美的。证明:如果不优美,那么补图中存在(a,b,c,d)使得((a,b),(b,c),(c,d)in E)并且((a,c),(b,d),(a,d) otin E),则原图中((a,c),(b,d),(a,d)in E)((a,b),(b,c),(c,d)in E),于是原图不优美,矛盾。

结论3:优美的图的原图和补图不能同时联通。

证明:考虑归纳。加入新点(v),假如之前的图(G)原图不连通,补图联通,假设现在原图联通,补图联通。(v)会向每个连通块(C_i)连至少一条边,且至少一个连通块有点没有和(v)直接相连,设这个连通块为(C_1)。在(C_1)中假如和(v)直接相连的点集合为(S),没有相连的点的集合为(T)。因为(C_1=Sigcup T)联通所以一定存在(xin S,yin T,(x,y)in E)。任取(C_2)中和(v)有连边的点(z)。于是此时有:((z,v),(v,x),(x,y)in E),然而((z,x),(v,y),(z,y))都没有出现。矛盾。

于是做法是:如果原图不连通,就按照原图连通块划分子问题,结果的生成函数加起来;如果补图不连通,就按照补图连通块划分子问题,结果的生成函数乘起来。(因为有不选的情况所以不是直接说的那样,非重点,不详细描述)

朴素的方法是先BFS判断连通性,然后在那个不连通的图中随便选个点然后BFS出个联通块,分出两个子问题做。暴力干时间(O(n^3))。后面考虑如何快速实现这个过程。如果每次能划分成两个子问题(X,Y),花不超过(O(|X||Y|))的时间,时间复杂度和树上背包是一样的(O(n^2))。合并信息不用超过这个时间,现在考虑如何在这个时间内划分出子问题。

结论4:优美的图的直径不超过(2)(图的直径是两两之间距离的最大值,距离为两点间最短路径长度)。根据优美的图定义容易推导得出。

于是BFS只需要遍历两层。如果选出个度数为(d)的点,从它开始BFS,那么花的时间是(O(dn))。在原图和补图中分别选出度数最小的点,遍历度数较小的,就可以花(O(min(d_0,d_1)n))时间得到连通性,接下来再用(O(dn))跑出连通块。因为最小度数小于最小连通块大小,所以时间也不超过(O(min(|X|,|Y|)n)=O(|X||Y|))

实现上注意要顺便维护度数,不要每次暴力算出来。


using namespace std;
#include <bits/stdc++.h>
#define N 8005
#define ll long long
#define mo 998244353
int n;
int e[N][N],deg[N];
void read(){
	scanf("%d",&n);
	static char str[N];
	for (int i=0;i<n;++i){
		scanf("%s",str);
		for (int j=0;j<n-i-1;++j)
			if (e[i][i+j+1]=e[i+j+1][i]=(isdigit(str[j/4])?str[j/4]-'0':str[j/4]-'A'+10)>>j%4&1)
				deg[i]++,deg[i+j+1]++;
	}
}
void add(int c[],int a[],int b[],int na,int nb){
	static int t[N];
	memset(t+1,0,sizeof(int)*(na+nb));
	for (int i=1;i<=na;++i) t[i]+=a[i];
	for (int i=1;i<=nb;++i) (t[i]+=b[i])%=mo;
	memcpy(c+1,t+1,sizeof(int)*(na+nb));
}
void multi(int c[],int a[],int b[],int na,int nb){
	static int t[N];
	add(t,a,b,na,nb);
	for (int i=1;i<=na;++i)
		for (int j=1;j<=nb;++j)
			t[i+j]=(t[i+j]+(ll)a[i]*b[j])%mo;
	memcpy(c+1,t+1,sizeof(int)*(na+nb));
}
int _q[N],_p[N];
void work(int[],int[],int);
bool find(int q[],int p[],int k,int r,int o){
	static int vis[N],BZ;
	vis[r]=++BZ;
	for (int i=0;i<k;++i)
		if (e[r][q[i]]==o){
			vis[q[i]]=BZ;
			for (int j=0;j<k;++j)
				if (e[q[i]][q[j]]==o)
					vis[q[j]]=BZ;
		}
	bool all=1;
	for (int i=0;i<k;++i)
		all&=(vis[q[i]]==BZ);
	if (all) return 0;
	static int t[N];
	int cl=0,cr=k;
	for (int i=0;i<k;++i)
		(vis[q[i]]==BZ?t[cl++]:t[--cr])=q[i];
	memcpy(q,t,sizeof(int)*k);
	for (int i=0;i<cl;++i)
		for (int j=cl;j<k;++j)
			if (e[q[i]][q[j]])
				deg[q[i]]--,deg[q[j]]--;
	int *pl=p,*pr=p+cl;
	work(q,pl,cl);
	work(q+cl,pr,k-cl);
	if (o==0)
		multi(p,pl,pr,cl,k-cl);
	else
		add(p,pl,pr,cl,k-cl);
	return 1;
}
void work(int q[],int p[],int k){
	if (k==1){
		p[1]=1;
		return;
	}
	int r[2]={-1,-1},dr[2]={n,n};
	for (int i=0;i<k;++i){
		int d=deg[q[i]];
		if (d<dr[1]) r[1]=q[i],dr[1]=d;
		if (k-1-d<dr[0]) r[0]=q[i],dr[0]=k-1-d;
	}
	int fir=(dr[0]<dr[1]?0:1);
	if (!find(q,p,k,r[fir],fir))
		find(q,p,k,r[fir^1],fir^1);
}
int main(){
	read();
	for (int i=0;i<n;++i)
		_q[i]=i;
	work(_q,_p,n);
	for (int i=1;i<=n;++i)
		printf("%d ",_p[i]);
	return 0;
}
原文地址:https://www.cnblogs.com/jz-597/p/14833630.html