• 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 的感觉。

  • 相关阅读:
    redis中文API
    基于redis的分布式锁
    redis安装使用配置
    windows常用命令
    curl命令详解
    linux远程连接
    memcache在大型网站的应用策略
    缓存使用的一些注意事项
    记录memcache分布式策略及算法
    U盘安装Centos7.0图解
  • 原文地址:https://www.cnblogs.com/Kiritsugu/p/13364000.html
Copyright © 2020-2023  润新知