题意:给定一个n(1 <= n <= 100)个点,m条边的无向图,每条边只能选1个点,输出最多能选的点数,并打印方案。
分析:
本周7道作业最难的题,是一个一般图的最大独立集(最大独立集含义如题),这种问题一般把原图所有未相连的点连接,再把原图所有相连点的边去掉,也就是建一个反图,然后求最大团,所谓最大团,就是求一个最大的子图,使得其构成一个完全图,额...所谓完全图,就是任意两点之间都有边相连的图。
最大团的求法就是用dfs,每次枚举要加进去的点,如果这个点和已经加进去的点都有边相连,那么就加进去,如果不能加进去任何点了,现在存的就是一个团。
加了若干剪枝(虽然说没这些剪枝估计也能过):
1.每次只枚举当前加进去的结点相邻的点,因为不相邻的点肯定不能加(可行性剪枝)。
2.枚举的点序号大于当前加进去的点才考虑(避免重复搜索)。
3.如果最好情况下也不能优于当前解,直接退出(最优性剪枝)。
然后成功达到G++排行榜82名,GG。
打印方案的时候就是如果当前是最优解的话,就打印,并把fin(完成标志)设为1.
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
int T, n, m, x, y, cnt, ans, ansi, fin, vis[105], mat[105][105], to[10005], nxt[10005], hd[105];
vector<int> v;
void add(int x, int y) {
to[++cnt] = y;
nxt[cnt] = hd[x];
hd[x] = cnt;
}
int dfs(int x, int cnt, int fl) {
if(n - x + cnt <= ans) return 0;
v.push_back(x);
vis[x] = 1;
int ok = 0, anss = 0;
for(int i = hd[x]; i; i = nxt[i]) if(!vis[to[i]] && to[i] > x){
int okk = 1;
for(int j = 0; j < v.size(); j++) if(!mat[to[i]][v[j]]) {
okk = 0;
break;
}
if(okk) ok = 1, anss = max(anss, dfs(to[i], cnt+1, fl));
}
if(!ok && cnt == ans && !fin && fl) {
fin = 1;
for(int i = 0; i < v.size(); i++) printf("%d ", v[i]);
}
vis[x] = 0;
v.pop_back();
if(!ok) return cnt;
return anss;
}
int main() {
scanf("%d", &T);
while(T--) {
cnt = 0;
memset(hd, 0, sizeof hd);
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
mat[i][j] = 1;
while(m--) scanf("%d%d", &x, &y), mat[x][y] = 0, mat[y][x] = 0;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
if(mat[i][j]) add(i, j);
ans = 0;
for(int i = 1; i <= n; i++) {
int tmp = dfs(i, 1, 0);
if(ans < tmp) ans = tmp, ansi = i;
}
printf("%d\n", ans);
fin = 0;
dfs(ansi, 1, 1);
printf("\n");
}
return 0;
}