题意:给定一个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; }