POJ 1419 【最大团模板解析】

传送门

题目描述(中文

由于一个小小的失误,你的电脑被“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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值