hdu 5952 Counting Cliques 暴搜+剪枝优化(邻接链表建图,邻接矩阵重建图,另类ac哦)

题目描述:

Problem Description 
A clique is a complete graph, in which there is an edge between every pair of the vertices. Given a graph with N vertices and M edges, your task is to count the number of cliques with a specific size S in the graph.

Input 
The first line is the number of test cases. For each test case, the first line contains 3 integers N,M and S (N ≤ 100,M ≤ 1000,2 ≤ S ≤ 10), each of the following M lines contains 2 integers u and v (1 ≤ u < v ≤ N), which means there is an edge between vertices u and v. It is guaranteed that the maximum degree of the vertices is no larger than 20.

Output 
For each test case, output the number of cliques with size S in the graph.

Sample Input

3
4 3 2
1 2
2 3
3 4
5 9 3
1 3
1 4
1 5
2 3
2 4
2 5
3 4
3 5
4 5
6 15 4
1 2
1 3
1 4
1 5
1 6
2 3
2 4
2 5
2 6
3 4
3 5
3 6
4 5
4 6
5 6

Sample Output

3
7
15

Source 
2016ACM/ICPC亚洲区沈阳站-重现赛(感谢东北大学)

题目大意:

给你一个n个点,m条边的图,问你这个图中有多少个点数为s的完全图

题解:

正常暴力去一个点一个点的去暴搜,一定会超时,所以我们必须加一些剪枝。我的思路就是让我们构成的团中所有的点呈升序(这样可以避免重复团的出现)。还要在搜的时候判断一下此时这个点与之前存的点有没有边,如果其中有一个没有那就回溯不往下继续搜。刚开始我用的是邻接链表建图,但是正好我输入的样例所有的边都是1 2,1 3,1 4这样升序存的,当邻接链表遍历的时候就会从4,3,2往下遍历,导致之前我说团中成升序的情况恰好可以通过。但是一旦你的测试样例是这样输入的话1 4,1 3,1 2,我们下面代码的那个剪枝就会导致bug(因为这个导致wa了好久,以至于比赛的时候没有ac,还是菜啊)。所以我的想法就是将边排序,用邻接矩阵来存,在dfs时倒着将所连的点遍历,这样就不会wa了。  这个方法有点复杂,不向别人的姿势优美,而且对比别人ac的我的时间要比他们慢,但绝对原创哦。

代码如下(有注释可以好好参考一下):

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<queue>
#include<stack>
#include<vector>
#include<set>
#include<map>
#include<list>
#define mem(x,y)memset(x,y,sizeof(x))
#define max(a,b)(a)>(b)?(a):(b)
#define min(a,b)(a)<(b)?(a):(b)
#define INF 0x1f1f1f1f
#define eps 1e-10
#define M 10000000007
using namespace std;
const int maxn=1005;
struct qxw
{
    int point,next;
};//  邻接链表
vector <int> G[maxn];//   邻接矩阵
qxw a[maxn*10];
int n,m,s,tot,sum,ss;
int head[maxn];
int vis[maxn][maxn];//  标记两点之间是否有边
int b[maxn];//  这个数组是每次团中存的点
int c[maxn];
int V[maxn];
void init()//   初始化函数
{
    int i;
    for(i=0;i<=n;i++)
    {
        G[i].clear();
    }
    mem(b,0);
    mem(V,0);
    mem(head,-1);
    mem(vis,0);
    tot=0;
}
void add(int xx,int yy)//   邻接链表加边
{
    a[tot].point=yy;
    a[tot].next=head[xx];
    head[xx]=tot++;
}
void dfs(int u,int num,int bnt)//  暴搜找团
{
    if(num>s)return;
    int i,j;
    b[bnt]=u;
    V[u]=1;
    for(i=0;i<bnt;i++)//  剪枝的一部分:
    {                 //判断此时这个点和之前存在b数组的点有没有边相连
        if(!vis[u][b[i]]||!vis[b[i]][u])//如果无边直接回溯
        {
            return ;
        }
    }
    if(num==s)//  如果可以构成一个团,sum++
    {
        sum++;
        return ;
    }
    for(i=G[u].size()-1;i>=0;i--)
    {
        int v=G[u][i];
        if(v<u)return ;//  这里也是一个剪枝,防止出现重复团的情况,让所有的团中的点呈升序排列
        if(!V[v])
        {
            dfs(v,num+1,bnt+1);
            V[v]=0;
        }
    }
}
int main()
{
    int T,i,j;
    scanf("%d",&T);
    while(T--)
    {
        init();
        scanf("%d %d %d",&n,&m,&s);
        int x,y;
        for(i=0;i<m;i++)
        {
            scanf("%d %d",&x,&y);
            if(vis[x][y]||vis[y][x])//   避免重边
            {
                continue;
            }
            add(x,y);//刚开始使用邻接链表建图
            add(y,x);
            vis[x][y]=1,vis[y][x]=1;//  有边的话标记为一
            vis[x][x]=1,vis[y][y]=1;
        }
        int cnt;
        //  下面代码实现的是:遍历所有边,将每个点相连的点从小到大,用邻接矩阵重新建图
        for(i=1;i<=n;i++)
        {
            cnt=0;
            for(j=head[i];j!=-1;j=a[j].next)
            {
                c[cnt++]=a[j].point;
            }
            sort(c,c+cnt);
            for(j=0;j<cnt;j++)
            {
                G[i].push_back(c[j]);
            }
        }
        sum=0;
        ss=0;
        //  为了让所有团中的点呈升序,所以(n-s+1)后面的点没有必要在遍历
        for(i=1;i<=n-s+1;i++)
        {
            mem(V,0);
            dfs(i,1,0);
        }
        printf("%d\n",sum);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值