传送门
题目描述(中文)
由于一个小小的失误,你的电脑被“WannaCry”病毒袭击了,所有文件都被加密了,不过作为一只ACM大佬,你只用了7天就分解了病毒的RSA4096公钥,计算出了病毒的私钥并成功解密了文件,但你错过了这7天的Codeforces比赛,这导致你的排名下降了,你十分生气,决定写一个更加强大的“WannaCry”来报复社会,于是,又花了7天的时间,你完成了你的加强版“WannaCry”,当你决定将其投入使用时,你发现校园网已经升级了,另一只ACM大佬写了一个智能程序来监控这个网络,我们可以将校园网看成一个无向图,计算机为网络中的节点,计算机之间的连接为网络中的路径,你发现一旦你的病毒感染了网络中两台直接相连的计算机,你的病毒就会立刻被大佬的智能程序发现并完全地消灭掉,现在,你需要计算你的病毒可以感染计算机的最大数量
输入格式
第一行是一个整数n,代表有n组测试数据,对每组测试数据,第一行有两个数字x和y,代表校园网有x(1<=x<=100)个计算机节点(这些计算机的编号为从1到x)和y条路径组成,接下来y行,每行有两个数字a和b(1<=a,b<=x),代表编号为a的计算机和编号为b的计算机直接相连
输出格式
对每组测试数据,输出两行,第一行的最多能感染的计算机数量,第二行为这些计算机的编号(任意输出一组)
最大团定义
一个无向图 G=(V,E),V 是点集,E 是边集。取 V 的一个子集 U,若对于 U 中任意两个点 u 和 v,有边 (u,v)∈E,那么称 U 是 G 的一个完全子图。 U 是一个团当且仅当 U 不被包含在一个更大的完全子图中。G的最大团指的是定点数最多的一个团。
图最大团和最大独立集的关系:
①图的最大独立集点的数量=补图的最大团点的数量
②图的最大团点的数量=补图的最大独立集点的数量
最大团算法的思路
初始化:
从一个点 u 开始,把这个点加入集合 U 中。将编号比它大的且和它相连的点加入集合 S1 中,为了方便,将集合 S1 中的点有序,让他们从小到大排列,进行第一遍 DFS
第一遍 DFS :
从 S1 中选择一个点 u1,遍历 S1 中,所有编号比 u1 大且和 u1 相连的点,其实也就是排在 u1 后面,并且和 u1 相连的点,将它们加入集合 S2 中。同理,让 S2 中的点也按照编号也从小到大排列。将 u1 加入集合 U 中,进行第二遍 DFS
第二遍 DFS :
从 S2 中选择一个点 u2,遍历 S2 中,所有排在 u2 后面且和 u2 相连的点,并把它们加入集合 S3 中,让 S3 中的点按照编号从小到大排列,将 u2 加入集合 U 中进行第三遍 DFS
第三遍 DFS :
从 S3 中选择一个点 u3,遍历 S3 中,所有排在 u3 后面且和 u3 相连的点,并把它们加入集合 S4 中,让 S4 中的点按照编号从小到大排列,将 u3 加入集合 U 中进行第四遍 DFS
......
最底层的 DFS :
当某个 S 集合为空时,DFS 过程结束,得到一个只用后面几个点构成的完全子图,并用它去更新只用后面几个点构成的最大团。退出当前 DFS,返回上层 DFS,接着找下一个完全子图,直到找完所有的完全子图
算法优化
假设我们当前处于第 i 层 DFS,现在需要从 Si 中选择一个 ui,把在 Si 集合中排在 ui 后面的和 ui 相连的点加入集合 S(i+1) 中,把 ui 加到集合 U 中。
剪枝①:如果 U 集合中的点的数量+1(选择 ui 加入 U 集合中)+Si 中所有 ui 后面的点的数量 ≤当前最优值,不用再DFS了
剪枝②:注意到我们是从后往前选择 u 的,也就是说,我们在 DFS 初始化的时候,假设选择的是编号为 x 的点,那么我们肯定已经知道了用 [x+1, n] ,[x+2, n],[x+3, n] ...[n,n] 这些区间中的点能构成的最大团的数量是多大,如果 U 集合中的点的数量+1(理由同上)+[ui, n]这个区间中能构成的最大团的顶点数量 ≤ 当前最优值,不用再 DFS了
剪枝③:如果 DFS 到最底层,我们能够更新答案,不用再 DFS 了,结束整个 DFS 过程,也不再返回上一层继续DFS了
例题代码
///#include<bits/stdc++.h>
///#include<unordered_map>
///#include<unordered_set>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
#include<bitset>
#include<set>
#include<stack>
#include<map>
#include<list>
#include<new>
#include<vector>
#define MT(a,b) memset(a,b,sizeof(a));
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double pai=acos(-1.0);
const double E=2.718281828459;
int n,m;
int maxn; ///最大团的点数
int dp[105]; ///dp[i]表示从i到n中的最大团的节点数。
int maps[105][105]; ///邻接矩阵
int que[105][105];
vector<int>ans,q; ///ans答案 q临时答案
int dfs(int up,int step) ///up表示集合点的数量,step第几层DFS
{
if(up==0)
{
if(step>maxn) ///剪枝3
{
maxn=step;
ans=q;
return 1;
}
return 0;
}
for(int i=1;i<=up;i++)
{
int s=que[step][i];
if(step+n-s+1<=maxn||step+dp[s]<=maxn)///剪枝1和2
return 0;
int cnt=0;
for(int j=i+1;j<=up;j++)///找结点比
{
if(maps[s][que[step][j]])
que[step+1][++cnt]=que[step][j];
}
q.push_back(s);
if(dfs(cnt,step+1))
return 1;
q.pop_back();
}
return 0;
}
int main()
{
int t;
int s,e;
scanf("%d",&t);
while(t--)
{
maxn=0;
fill(maps[0],maps[0]+sizeof(maps)/sizeof(int),1);
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d %d",&s,&e);
maps[e][s]=maps[s][e]=0;///建立补图
}
for(int i=n;i>=1;i--)
{
q.clear();
q.push_back(i);
int up=0;
for(int j=i+1;j<=n;j++)
if(maps[i][j])
que[1][++up]=j;
dfs(up,1);
dp[i]=maxn;
}
printf("%d\n",maxn);
for(int i=0;i<maxn;i++)
{
printf("%d",ans[i]);
printf(i==maxn-1?"\n":" ");
}
}
return 0;
}
参考博客:http://www.cnblogs.com/zhj5chengfeng/archive/2013/07/29/3224092.html
https://blog.csdn.net/whosemario/article/details/8513836