https://codeforces.com/contest/1385
本来是练习Java的,后面三道题还是上了C++,顺便学了一遍2SAT。这次可以说是拓扑排序专场,E题用了BFS,G题用DFS,各有千秋,记录一下最后三题。
Problem E
给出一个有向图的边,两种情况:有的边已经指定了方向(u -> v),有的边待指定方向(u - v)。现尝试为所有边确定方向,使这个图成为DAG。
tutorial: 简单地进行拓扑排序,若已有环路肯定无法成为DAG,若无则将待指定的方向按拓扑序指向(逆拓扑序可能生成环路)。
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef pair<int , int> pii; 5 6 const int mxsz = 200000 + 5; 7 vector<vector<int>> G; 8 vector<pii> Bi; 9 int vec2Order[mxsz]; 10 int indeg[mxsz]; 11 12 bool topo() { 13 queue<int> que; 14 for(int i = 1; i < G.size(); ++i) { 15 if(indeg[i] == 0) 16 que.push(i); 17 } 18 int topoInd = 0; 19 while (!que.empty()) { 20 int cur = que.front(); 21 que.pop(); 22 vec2Order[cur] = topoInd++; 23 24 for(int v : G[cur]) { 25 indeg[v] --; 26 if(indeg[v] == 0) 27 que.push(v); 28 } 29 } 30 cerr << topoInd << "," << G.size() << endl; 31 return topoInd + 1 == G.size(); 32 } 33 34 int main() { 35 int t; 36 scanf("%d", &t); 37 while(t--) { 38 int n, m; 39 scanf("%d%d", &n, &m); 40 G.assign(n+1, vector<int>()); 41 Bi.clear(); 42 memset(indeg, 0, sizeof(indeg)); 43 44 for(int i = 0 ;i < m; ++i) { 45 int tmp, u, v; 46 scanf("%d%d%d", &tmp, &u, &v); 47 if(tmp) { 48 G[u].push_back(v); 49 indeg[v] ++; 50 } 51 else 52 Bi.push_back(make_pair(u,v)); 53 } 54 if(topo()) { 55 puts("Yes"); 56 for(int i = 0; i < G.size(); ++i) { 57 for(int j = 0;j < G[i].size(); ++j) 58 printf("%d %d ", i, G[i][j]); 59 } 60 61 for(int i = 0; i < Bi.size(); ++i) { 62 int u = Bi[i].first, v = Bi[i].second; 63 if(vec2Order[u] > vec2Order[v]) 64 swap(u, v); 65 printf("%d %d ", u, v); 66 } 67 } else { 68 puts("NO"); 69 } 70 71 } 72 return 0; 73 }
Problem F
给出一棵树和数字k,要求每一次刚好删除k个叶子节点,问能删除几次。
tutorial: 意思上是简单的implementation,直接模拟删除的过程即可。但是实现上需要注意表示删除叶子结点后的树的结构变化,即新的叶子节点生成以及其父节点,以及特例情况。我的实现略微粗糙,可以优化。
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 5 const int mxsz = 200000 + 5; 6 7 int cntleaves[mxsz]; 8 vector<vector<int>> G; 9 set<int> Leaves; 10 11 int main() { 12 int t; 13 scanf("%d", &t); 14 while (t--) { 15 int n, k; 16 scanf("%d%d", &n,&k); 17 18 fill(cntleaves, cntleaves + n, 0); 19 G.assign(n, vector<int>()); 20 Leaves.clear(); 21 for(int i = 0;i < n-1; ++i) { 22 int u, v; 23 scanf("%d%d", &u, &v); 24 u--; v--; 25 G[u].push_back(v); 26 G[v].push_back(u); 27 } 28 29 //special when n = 2, k = 1 30 if(k == 1) { 31 printf("%d ", n - 1); 32 continue; 33 } 34 35 int ans = 0; 36 queue<int> vec; 37 for(int i = 0 ;i < G.size(); ++i) { 38 if(G[i].size() == 1) { 39 cntleaves[G[i][0]] ++; 40 Leaves.insert(i); 41 } 42 } 43 for(int i = 0; i < n; ++i) { 44 if(cntleaves[i] > 0) 45 vec.push(i); 46 } 47 while (!vec.empty()) { 48 int cur = vec.front(); 49 vec.pop(); 50 51 int moves = cntleaves[cur] / k; 52 cntleaves[cur] %= k; 53 int cntDelLeaves = moves * k; 54 int cntDegree = 0; 55 56 if(cntDelLeaves > 0) { 57 for(int i = 0 ; i < G[cur].size(); ++i) { 58 if(G[cur][i] != -1) { 59 cntDegree ++; 60 if(Leaves.count(G[cur][i]) && cntDelLeaves) { 61 cntDelLeaves --; 62 G[cur][i] = -1; 63 } 64 } 65 } 66 if(cntDegree - moves * k == 1) { 67 for(int i = 0 ;i < G[cur].size(); ++i) { 68 if(G[cur][i] != -1) { 69 Leaves.insert(cur); 70 cntleaves[G[cur][i]] ++; 71 vec.push(G[cur][i]); 72 break; 73 } 74 } 75 } 76 } 77 78 ans += moves; 79 } 80 81 printf("%d ", ans); 82 } 83 return 0; 84 }
Problem G
给出两行数列,可以交换同一列的上下两个数字,问能否将其转换为两行permutation,并要求回答最小交换次数的解。
这题有意思,由于正在看SMT,Satisfiability,正好通过这题学习2SAT问题的相关算法。
1 #include <bits/stdc++.h> 2 3 using namespace std; 4 5 typedef pair<int, int> pii; 6 vector<vector<pii>> indWhere; 7 set<int> NotChange; 8 9 vector<vector<int>> g,gt; 10 vector<int> Order; 11 vector<bool> Used; 12 vector<int> Comp; 13 vector<bool> Assign; 14 int ans; 15 16 inline int TrueVec(int x) {return 2 * x;} 17 inline int FalseVec(int x) {return 2 * x + 1;} 18 19 void dfs1(int u) { 20 Used[u] = true; 21 22 for(int v : g[u]){ 23 if(!Used[v]) 24 dfs1(v); 25 } 26 Order.push_back(u); 27 } 28 29 void dfs2(int u, int cc) { 30 Comp[u] = cc; 31 for(int v : gt[u]) { 32 if(Comp[v] == -1) 33 dfs2(v ,cc); 34 } 35 } 36 37 bool solve(int n) { 38 Order.clear(); 39 Used.assign(2 * n, false); 40 Comp.assign(2 * n, -1); 41 Assign.assign(n, false); 42 ans = 0; 43 44 for(int i = 0 ;i < g.size(); ++i) { 45 if(!Used[i]) { 46 dfs1(i); 47 } 48 } 49 int cntComp = 0; 50 for(int i = 0 ;i < Order.size(); ++i) { 51 int u = Order[Order.size() - 1 - i]; 52 if(Comp[u] == -1) 53 dfs2(u, cntComp++); 54 } 55 56 for(int i = 0 ;i < Comp.size(); i += 2) { 57 if(Comp[i] == Comp[i+1]) return false; 58 Assign[i/2] = Comp[i] > Comp[i+1]; 59 if(Assign[i/2]) ans++; 60 } 61 62 return true; 63 } 64 65 int main(){ 66 int t; 67 scanf("%d", &t); 68 while (t--) { 69 ans = 0; 70 int n; 71 scanf("%d", &n); 72 indWhere.assign(n, vector<pii>()); 73 for(int i = 0 ;i < 2; ++i) { 74 for(int j = 0;j < n ; ++j) { 75 int tmp; 76 scanf("%d", &tmp); 77 tmp --; 78 indWhere[tmp].push_back(make_pair(i, j)); 79 if(indWhere[tmp].size() > 2) { 80 ans = -1; 81 } 82 } 83 } 84 // special 85 if(ans == -1) { 86 puts("-1"); 87 continue; 88 } 89 90 g.assign(2 * n, vector<int>()); 91 gt.assign(2 * n,vector<int>()); 92 NotChange.clear(); 93 94 for(int i = 0; i < n; ++i) { 95 int r0 = indWhere[i][0].first, c0 = indWhere[i][0].second; 96 int r1 = indWhere[i][1].first, c1 = indWhere[i][1].second; 97 if(c0 == c1) { // co is false 98 NotChange.insert(c0); // 99 g[TrueVec(c0)].push_back(FalseVec(c0)); 100 101 gt[FalseVec(c0)].push_back(TrueVec(c0)); 102 } else if(r0 == r1) { // c0 xor c1 is true; 103 g[TrueVec(c0)].push_back(FalseVec(c1)); 104 g[TrueVec(c1)].push_back(FalseVec(c0)); 105 g[FalseVec(c0)].push_back(TrueVec(c1)); 106 g[FalseVec(c1)].push_back(TrueVec(c0)); 107 108 gt[FalseVec(c1)].push_back(TrueVec(c0)); 109 gt[FalseVec(c0)].push_back(TrueVec(c1)); 110 gt[TrueVec(c1)].push_back(FalseVec(c0)); 111 gt[TrueVec(c0)].push_back(FalseVec(c1)); 112 } else { // c0 xor c1 is false 113 g[TrueVec(c0)].push_back(TrueVec(c1)); 114 g[TrueVec(c1)].push_back(TrueVec(c0)); 115 g[FalseVec(c0)].push_back(FalseVec(c1)); 116 g[FalseVec(c1)].push_back(FalseVec(c0)); 117 118 gt[TrueVec(c1)].push_back(TrueVec(c0)); 119 gt[TrueVec(c0)].push_back(TrueVec(c1)); 120 gt[FalseVec(c1)].push_back(FalseVec(c0)); 121 gt[FalseVec(c0)].push_back(FalseVec(c1)); 122 } 123 } 124 125 126 127 if(solve(n)) { 128 if(ans <= n - NotChange.size() - ans) { 129 printf("%d ", ans); 130 for(int i = 0, cntAns = 0;i < Assign.size(); ++i){ 131 if(Assign[i] && NotChange.count(i) == 0) printf("%d%c", i+1, cntAns++==ans-1 ?' ':' '); 132 } 133 } else { 134 ans = n - NotChange.size() - ans; 135 printf("%d ", ans); 136 for(int i = 0, j = 0; i < Assign.size(); ++i) { 137 if(Assign[i] == false && NotChange.count(i) == 0) 138 printf("%d%c", i+1, j++==ans-1?' ':' '); 139 } 140 if(ans == 0) puts(""); 141 } 142 143 } else { 144 puts("-1"); 145 } 146 147 148 } 149 return 0; 150 }
tutorial: 这题按照解2SAT问题可以得到一组解,但未必是最小交换次数的。其实类似这种想法,还是按照联通分量的思想,点与点之间用不同的边值代表其关系(异或和的真假),然后对一个点先随便赋一个值 b ,其所在联通分量的所有其他点的值必然也会确定,这时再贪心的比较最初赋值为~b 的结果是否更优(同一个联通分量其他点的值取逆即可),查完所有联通分量,即可得到解。
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef pair<int, int> pii; 5 6 vector<vector<pii>> indWhere; 7 vector<vector<pii>> G; 8 vector<bool> Used; 9 vector<vector<int>> comp; 10 vector<int> Assign; 11 vector<int> Ans; 12 13 void dfs1(int u, int cC) { 14 Used[u] = true; 15 for(pair<int, int> item : G[u]) { 16 int v = item.first; 17 if(!Used[v]) 18 dfs1(v, cC); 19 } 20 comp[cC].push_back(u); 21 } 22 23 void dfs2(int u, bool flag) { 24 Assign[u] = flag; 25 for (auto item : G[u]) { 26 if(Assign[item.first] == -1) { 27 dfs2(item.first, flag ^ item.second); 28 } 29 } 30 } 31 32 int main() { 33 int t; 34 scanf("%d", &t); 35 while(t--) { 36 int ans = 0; 37 int n; 38 scanf("%d", &n); 39 indWhere.assign(n, vector<pii>()); 40 for(int i = 0 ;i < 2; ++i) { 41 for(int j = 0;j < n ; ++j) { 42 int tmp; 43 scanf("%d", &tmp); 44 tmp --; 45 indWhere[tmp].push_back(make_pair(i, j)); 46 if(indWhere[tmp].size() > 2) { 47 ans = -1; 48 } 49 } 50 } 51 if(ans){ 52 puts("-1"); 53 continue; 54 } 55 G.assign(n, vector<pii>()); 56 for(int i = 0 ;i < n; ++i) { 57 int r0 = indWhere[i][0].first, c0 = indWhere[i][0].second; 58 int r1 = indWhere[i][1].first, c1 = indWhere[i][1].second; 59 60 if(c0 == c1) continue; 61 62 G[c0].push_back(make_pair(c1, r0 == r1)); 63 G[c1].push_back(make_pair(c0, r0 == r1)); 64 } 65 66 Used.assign(n, false); 67 comp.clear(); 68 for (int i = 0; i < n; ++i) { 69 int curComp = comp.size(); 70 comp.push_back(vector<int>()); 71 if(!Used[i]) 72 dfs1(i, curComp); 73 } 74 75 Assign.assign(n, -1); 76 Ans.clear(); 77 for(int i = 0 ;i < comp.size(); ++i) { 78 int tot = comp[i].size(); 79 if(tot <= 1) continue; 80 81 int cntTrue = 0; 82 dfs2(comp[i][0], true); 83 for (int j = 0; j < comp[i].size(); ++j) { 84 if(Assign[comp[i][j]]) 85 cntTrue++; 86 } 87 bool flag = cntTrue > tot - cntTrue; 88 for (int j = 0; j < comp[i].size(); ++j) { 89 if(flag^Assign[comp[i][j]]) 90 Ans.push_back(comp[i][j]); 91 } 92 } 93 94 if(Ans.empty()) { 95 printf("0 "); 96 } else { 97 printf("%d ", Ans.size()); 98 for (int i = 0; i < Ans.size(); ++i) { 99 printf("%d%c", Ans[i] + 1, i == Ans.size() - 1?' ':' '); 100 } 101 } 102 } 103 return 0; 104 }
只要permutation的每个数字刚好在两行数列中共出现两次,就必然有解。这其实从2SAT构图的过程中也可发现端倪,因为都是异或的关系,改为imperative后所有的 a 与 ~a之间都没有边,没有一对能直接连,这也会使没有一对在同一个联通分量里,整个图就像是一个个联通分量的团,这也解释了为什么会有多解(任何解取逆也是一个解),也解释了为什么按老方法赋值会不行。
后记
https://cp-algorithms.com/graph/2SAT.html
这是一篇讲2SAT讲的不错的文章,实现用Kosaraju's algorithm感觉比Tarjan写起来要更容易些,个人很喜欢利用 transpose graph 的感觉。