Codeforces Round #656

Posted kiritsugu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Codeforces Round #656相关的知识,希望对你有一定的参考价值。

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 }
Solution

 

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 }
Solution

 

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 }
Bad Answer

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 }
Solution

只要permutation的每个数字刚好在两行数列中共出现两次,就必然有解。这其实从2SAT构图的过程中也可发现端倪,因为都是异或的关系,改为imperative后所有的 a 与 ~a之间都没有边,没有一对能直接连,这也会使没有一对在同一个联通分量里,整个图就像是一个个联通分量的团,这也解释了为什么会有多解(任何解取逆也是一个解),也解释了为什么按老方法赋值会不行。

 

后记

https://cp-algorithms.com/graph/2SAT.html

这是一篇讲2SAT讲的不错的文章,实现用Kosaraju‘s algorithm感觉比Tarjan写起来要更容易些,个人很喜欢利用  transpose graph 的感觉。

以上是关于Codeforces Round #656的主要内容,如果未能解决你的问题,请参考以下文章

Codeforces Round #656 (Div. 3)

Codeforces Round #656 (Div. 3) A. Three Pairwise Maximums(思维/构造)

Codeforces Round #656 (Div. 3) E. Directing Edges

Codeforces Round #656 (Div. 3) E. Directing Edges

Codeforces Round #656 (Div. 3) E. Directing Edges

Codeforces Round #656 (Div. 3)E. Directing Edges(拓扑排序+构造dag图)