题目描述:
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;
}