【模板】无向图最大团-CF1105E.Helping Hiasat

传送门:cf1105E


题解

本质上就是求无向图最大点独立集。

无向图最大点独立集等于其补图的最大团。

求无向图的最大团Bron-Kerbosch算法:

本质上还是dfs,不过加了一些减枝。构造三个集合 a l l , s o m e , n o n e all,some,none all,some,none分别表示已经加入当前极大团的点集,未检查并可能加入当前团的点集(即与 a l l all all中所有点都有连边的点),已检查且不在/能加入当前团的点集。

每次在 s o m e some some中选出一个点 x x x加入 a l l all all,将 s o m e some some n o n e none none集合分别与 x x x的相邻点取交集(保证是完全图)。迭代进行检查。
检查完后退出,此时已经找出了 x x x所在的极大团,将该点从 a l l all all中删去,加入 n o n e none none(表示之后的检查中强制不再选择该点)。

s o m e some some n o n e none none均为空时,就得到了一个点集为 a l l all all的极大团,可以更新答案。若 s o m e some some为空,但 n o n e none none(强制不选的点)不为空,则完全可以将 n o n e none none中的点加入当前团,所以当前团不是极大团。

一个重要的剪枝:
假设当前层中取出 s o m e some some中点 x x x进行检查, s o m e some some集合与 x x x的相邻点取交后在下一层迭代中必然均会被检查到,这一层就不需要检查了。所以将 s o m e some some按点度降序排序,这样检查次数最少。

时间复杂度上限: O ( 3 n 3 ) O(3^{\frac n3}) O(33n)(不会证)


代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;

int ans,op,n,m,id[N],d[45];
int ch[N][26],cnt,num,g[45][45];
int som[45][45],all[45][45],non[45][45];
bool exi[45];char s[45];

inline bool cmp(int x,int y){return d[x]>d[y];}

void bronkerbosch(int pos,int al,int sn,int nn)
{
	if(!sn) {if(!nn) ans=max(ans,al);return;}
	int i,j,k,pv=som[pos][1],snn,nnn;
	memcpy(all[pos+1],all[pos],sizeof(int)*(al+1));
	for(i=1;i<=sn;++i) if(!g[pv][som[pos][i]]){
		j=som[pos][i];all[pos+1][al+1]=j;
		snn=nnn=0;
		for(k=1;k<=sn;++k) if((~som[pos][k])&&g[j][som[pos][k]])
		   som[pos+1][++snn]=som[pos][k];
		for(k=1;k<=nn;++k) if(g[j][non[pos][k]])
		   non[pos+1][++nnn]=non[pos][k];
		bronkerbosch(pos+1,al+1,snn,nnn);
		som[pos][i]=-1;non[pos][++nn]=j;
	}
}

int main(){
    int i,j,k,u;
    scanf("%d%d",&n,&m);
	for(;n;--n){
		scanf("%d",&op);
		if(op==1){
			for(i=1;i<=num;++i) if(exi[i])
			 for(j=i+1;j<=num;++j) if(exi[j])
			  g[i][j]=g[j][i]=1;
			memset(exi,false,sizeof(exi));
		}else{
			scanf("%s",s+1);
			k=strlen(s+1);
			for(u=0,i=1;i<=k;++i){
				if(!ch[u][s[i]-'a']) ch[u][s[i]-'a']=++cnt;
				u=ch[u][s[i]-'a'];
			}
			if(!id[u]) id[u]=++num;exi[id[u]]=1;
		}
	}
	n=num;
	for(i=1;i<=n;++i) if(exi[i])
		for(j=i+1;j<=n;++j) if(exi[j])
			  g[i][j]=g[j][i]=1;
	for(i=1;i<=n;++i)
	 for(j=1;j<=n;++j) if(i!=j){
	 	g[i][j]^=1;if(g[i][j]) d[j]++;
	 }
	for(i=1;i<=n;++i) som[0][i]=i;
	sort(som[0]+1,som[0]+n+1,cmp);
    bronkerbosch(0,0,n,0);
	printf("%d",ans);
	return 0;
}

学习链接:无向图的最大团/最大独立集 算法总结-SparkFucker

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值