要求同一个队伍里的人互相认识,则一对互相不认识或者单方面认识的人必须被分到不同的队伍。根据后面这条规则进行分组会比较简单
所以我们可以从一个人出发,递归寻找所有“存在着不互相认识”关系的人,形成一个图,再给这个图里的所有人加正反标记,保证相邻的人都有相反的标记。若存在相邻的人有着相同的标记,则输出无解。之后再根据正反标记,把他们放到相应的组里面。
这样就把人分成了很多个连通块,每一个连通块维护着一个临时的分组。现在我们只需将每个连通块中的两个组里的人放进最终答案的两个组,这里便有两个选择:1->1 2->2或者1->2 2->1。
动态规划要做的就是找出正确的放法,使得最终两组人数差最小。
后话:
这里动态规划的“最优子结构”的感觉并不明显。动态规划的过程中,没有一个明确的目标,而只是将所有的可能性列举出来,直到最后才从所有的可能解中选出最优解。
Rujia将这道题和0-1背包进行类比。但是在0-1背包中,目标很明确:装的物品总价值最大。因此每次决策都要从两种决策中选出使得物品总价值最大的。
相比之下,这道题的动态规划,不应该从两种决策中选出“两队人数差最小的”,如果用这种贪心方法,会丢失全局最优解。事实上,只有最后一个连通块的决策才有着明确的目标,动态规划的其他中间过程在做决策时,是无法判断优劣的,只能枚举所有可能性。
因此我觉得这道题和0-1背包是貌合神离。
dfs复杂度O(n)(每个人恰好访问一次),动态规划复杂度O(n^2)(枚举决策复杂度将是O(2^n),这里用动态规划的思想消除了重叠自问题,很好的降低了复杂度),总复杂度O(n^2)
Run Time: 0.062s
#define UVa "LT9-19.1627.cpp" // char fileIn[30] = UVa, fileOut[30] = UVa; #include<cstring> #include<cstdio> #include<vector> using namespace std; struct CC { vector<int> group[2]; void clear() { group[0].clear(); group[1].clear(); } }; //Global Variables. Reset upon Each Case! const int maxn = 100 + 5; int T, n, cccnt; int G[maxn][maxn]; int vis[maxn]; CC cc; vector<CC> ccs; int d[maxn][2*maxn]; ///// int dfs(int u, int flag) { vis[u] = flag; for(int v = 0; v < n; v ++) { if(u != v && !(G[u][v] && G[v][u])) { if(!vis[v] && !dfs(v, -flag)) return 0; if(vis[u] == vis[v]) return 0; } } if(flag == 1) cc.group[0].push_back(u); else cc.group[1].push_back(u); return 1; } void insert(vector<int>& src, vector<int>&targ) { for(int i = 0; i < src.size(); i ++) targ.push_back(src[i]); } void print_ans(int j) { vector<int> result[2]; for(int i = cccnt - 1; i >= 0; i --) { int diff = ccs[i].group[0].size() - ccs[i].group[1].size(); if(d[i][j] == 1) { insert(ccs[i].group[0], result[0]); insert(ccs[i].group[1], result[1]); j -= diff; } else if(d[i][j] == -1) { insert(ccs[i].group[1], result[0]); insert(ccs[i].group[0], result[1]); j += diff; } } printf("%d", result[0].size()); for(int i = 0; i < result[0].size(); i ++) { printf(" %d", result[0][i]+1); } printf("\n"); printf("%d", result[1].size()); for(int i = 0; i < result[1].size(); i ++) { printf(" %d", result[1][i]+1); } printf("\n"); } void solve() { memset(d, 0, sizeof(d)); cccnt = ccs.size(); for(int i = 0; i < cccnt; i ++) { int diff = ccs[i].group[0].size() - ccs[i].group[1].size(); for(int j = 0; j <= 2*n; j ++) { if(i == 0) { d[i][n + diff] = 1; d[i][n - diff] = -1; break; } else { if(d[i-1][j]) { d[i][j + diff] = 1; d[i][j - diff] = -1; } } } } for(int i = 0; i <= n; i ++) { if(d[cccnt - 1][n+i]) { print_ans(n+i); return; } if(d[cccnt - 1][n-i]) { print_ans(n-i); return; } } } int main() { scanf("%d", &T); while(T--) { memset(G, 0, sizeof(G)); ccs.clear(); scanf("%d", &n); for(int i = 0; i < n; i ++) { int b; while(scanf("%d", &b) && b) G[i][b-1] = 1; } memset(vis, 0, sizeof(vis)); int ok = 1; for(int i = 0; i < n; i ++) { if(!vis[i]) { cc.clear(); if(!dfs(i, 1)) { ok = 0; break; } ccs.push_back(cc); } } if(!ok) printf("No solution\n"); else solve(); if(T) printf("\n"); } return 0; }