UOJ632. 【UR #21】挑战最大团
优美的图定义为:对于任意存在边\((a,b),(b,c),(c,d)\)的四个点,\((a,c),(b,d),(a,d)\)不能同时不出现。
现在给出一个优美的图,要对\(k\in [1,n]\)算大小为\(k\)的团的个数。
\(n\le 8000\)
结论1:优美的图的子图一定是优美的。(显然)
结论2:优美的图的补图一定是优美的。证明:如果不优美,那么补图中存在\(a,b,c,d\)使得\((a,b),(b,c),(c,d)\in E\)并且\((a,c),(b,d),(a,d)\notin 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=S\bigcup T\)联通所以一定存在\(x\in S,y\in 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;
}