• 2020-2021 ICPC, NERC, Northern Eurasia Onsite BEIJ 题解


    B. Button lock

    题意:有 (d) 个 01 按键以及一个 reset 按键,你需要把所有题目给定的 (n) 个密码全部表示一遍。只有按下 reset 按键后才能使所有 01 按键弹回。试使得按键次数最少。

    做法:可以观察到 (ans = sum_{u in endvertex} (bitcount(u) + 1) - 1),接下来要尽可能使 ans 最小。

    二分图匹配算法可以保证其路径数最少,接下来我们考虑二分图匹配与匈牙利算法。

    先设左边的点集为 S,右边的点集为 T。假如做完了二分图匹配,那么若左边点集 S 中有未匹配到的点,那么说明这个点是一条路径的结尾。这样,答案变成了使得左边点集 S 中未匹配的点的 1 的个数之和最少。

    由于在匈牙利算法中,我们依次枚举左侧点集中的点,一个之前匹配到的点不可能再失配,而一个失配的点在之后还是失配。这样,可以贪心地将所有密码按照 1 的个数从大到小排序,然后再做匈牙利算法。二分图匹配算法可以使路径数最少,而上述排序使得失配的左侧点的对答案的贡献和尽可能小。

    时间复杂度是 (O(VE) = O(2^d3^d) = O(6^d))

    上面少证明了一个东西:是否 ans 最小的时候满足路径数最少呢?反证法,若 ans 最小的时候路径数不是最小的,那么可想而知是在二分图匹配中有一些更多的点失配了,在最大匹配的时候失配的点仍然失配;或是这个点虽然匹配上了,但是含有更多 1 的点失配了。这样答案只会比满足路径数最少的条件时的答案更大。

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 1030;
    const int D = 15;
    
    #define IL inline
    #define pb push_back
    #define mk make_pair
    #define fi first
    #define se second
    
    #define dbg1(x) cout << #x << " = " << x << ", "
    #define dbg2(x) cout << #x << " = " << x << endl
    
    int d, n;
    int a[N];
    
    IL bool in(int x, int y) {
      return (x & y) == x;
    }
    
    IL int bitcount(int x) {
      return x == 0 ? 0 : bitcount(x >> 1) + (x & 1);
    }
    
    struct Hungary {
      int n, m;
      int Left[N], Right[N];
      bool S[N], T[N];
      vector<int> G[N];
    
      IL void init(int n, int m) {
        this -> n = n; this -> m = m;
        fill(Left, Left + 1 + m, 0);
        fill(Right, Right + 1 + n, 0);
        fill(S, S+n+1, 0);
        fill(T, T+m+1, 0);
        for(int i=1;i<=n;i++) G[i].clear();
      }
    
      IL void AddEdge(int u, int v) {
        G[u].pb(v);
      }
    
      bool match(int u) {
        //dbg2(u);
        S[u] = true;
        for(auto v : G[u]) if(!T[v]) {
    	//dbg1(u); dbg2(v);
    	    T[v] = true;
    	    if(Left[v] == 0 || match(Left[v])) {
    	      Left[v] = u; Right[u] = v;
    	      return true;
    	    }
        }
        return false;
      }
    
      int gao() {
        int ans = 0;
        fill(Left, Left+1+m, 0);
        fill(Right, Right + 1 + n, 0);
        for(int i=1;i<=n;i++) {
          fill(T, T+1+m, 0);
          if(match(i)) ans++;
        }
        return ans;
      }
    
      vector<int> ans;
    
      int lb(int x) { return x & (-x);}
      
      void dfs(int u) {
        int v = Right[u];
        //dbg1(u); dbg2(v);
        if(v == 0) { ans.pb(0); return;}
        for(int val = (a[u] ^ a[v]), j = __lg(val); val; val -= (1 << j), j = __lg(val)) {
          //dbg1(val); dbg2(j);
          ans.pb(d - j);
        }
        dfs(v);
      }
      
      void solve() {
        //puts("wtf");
        int matchcnt = gao();
        //dbg1(matchcnt);
        for(int i=1;i<=n;i++) {
          if(Left[i] != 0) continue;
          for(int val = a[i], j = __lg(val); val; val -= (1 << j), j = __lg(val)) {
    	//dbg1(i); dbg1(val); dbg2(j);
    	      ans.pb(d - j);
          }
          dfs(i);
        }
        printf("%d
    ", (int)ans.size()-1);
        for(int i=0;i<(int)ans.size()-1;i++) {
          if(ans[i] == 0) putchar('R');
          else printf("%d", ans[i] - 1);
          printf("%c", " 
    "[i == (int)ans.size() - 2]);
        }
      }
    };
    
    Hungary lpr;
    
    int main() {
      //freopen("B2.in", "r", stdin);
      //freopen("B2.out", "w", stdout);
      scanf("%d%d", &d, &n);
      lpr.init(n, n);
      for(int i=1;i<=n;i++) {
        for(int j=1;j<=d;j++) {
          int v; scanf("%1d", &v);
          a[i] = (a[i] << 1) | v;
        }
      }
    
      sort(a+1, a+1+n, [](int a, int b) { return bitcount(a) > bitcount(b);});
    
      //for(int i=1;i<=n;i++) {dbg1(i); dbg2(a[i]);}
      
      for(int i=1;i<=n;i++) {
        for(int j=1;j<i;j++) {
          if(!in(a[i], a[j])) continue;
          lpr.AddEdge(i, j);
        }
      }
    
      lpr.solve();
      
      return 0;
    }
    

    E. Equilibrium Point //

    题意:略

    前置知识:多边形的重心。每个三角形的

    做法:可以发现合法括号序列的个数是卡特兰数。在 n = 18 的时候,卡特兰数为 477638700。那么,是否可以使用类似卡特兰数定义式的方式去做这个题呢?PS:(f(n+1) = f(0)f(n) + f(1)f(n-1) + ... + f(n)f(0))

    然后可以发现似乎有一些情况下重心是一样的。也就是说可以把一些重心一样的括号序列去掉。

    由卡特兰递推式,我们考虑现在有 L 和 R 两个括号序列,那么接下来我们将它们合并起来,新的括号序列的重心是什么样子的呢?可以发现新的括号序列跟原来的括号序列所围成的面积 (m_L, m_R),原来的重心 ((x_L, y_L), (x_R, y_R)),以及 L 的长度 (2i) 相关。

    [m_{new} = m_L + m_R \ x_{new} = x_L frac{m_L}{m_{new}} + (x_R+2i) frac{m_R}{m_{new}} \ y_{new} = y_L frac{m_L}{m_{new}} + y_R frac{m_R}{m_{new}} ]

    还有一种情况是假如现在只有 L 括号序列,那么新的括号序列为 (L)

    [m_{new} = m_L + 2i - 1 \ x_{new} = (x_L + 1) frac{m_L}{m_{new}} + i frac{2i-1}{m_{new}} \ y_{new} = (y_L + 1) frac{m_L}{m_{new}} + frac{i - frac{2}{3}}{2i-1} cdot frac{2i-1}{m_{new}} ]

    也就是说,我们需要搞出来有 (i) 对括号的所有三元组 ((m, x, y)) 。通过类似卡特兰数定义式的枚举方法,可以预处理出来所有有 (i) 对括号情况下的三元组 ((m,x,y))。为了使得三元组唯一,请使用手写哈希表而不是STL,否则会TLE。这样,跑完之后发现只枚举了 79079257 次。即可解开此题。

    另外,由于跑三元组的时候一方面由于精度问题,另一方面由于运算速度问题,最好避免使用浮点数。所以在维护三元组的时候,我们维护的东西其实是 ((m, mx, 3my)),具体式子请自行带入上方的式子推一推。

    #include <bits/stdc++.h>
    using namespace std;
    
    #define mk make_pair
    #define dbg1(x) cout << #x << " = " << x << ", "
    #define dbg2(x) cout << #x << " = " << x << endl
    
    typedef tuple<int, int, int> tiii;
    typedef pair<tiii, tiii> pi6;
    typedef long long ll;
    
    const int mod = 10000007;
    int cnt = 0;
    int n;
    double x, y;
    
    struct Hash_table {
        int n, now;
        int hd[mod];
        int nxt[mod];
        int tim[mod];
        ll v[mod];
        void init() { n = 0; now++;}
        bool insert(ll val) {
            int s = val % mod;
            if(tim[s] != now) { tim[s] = now; hd[now] = 0;}
            for(int i=hd[s]; i!=0; i=nxt[i]) {
                if(v[i] == val) return false;
            }
            nxt[n + 1] = hd[s];
            v[n + 1] = val;
            hd[s] = ++n; 
            return true;
        }
    }HASH;
    
    vector<tuple<int, int, int> > a[20];
    
    
    string solve(int i, tiii p) {
        if(i == 0) return "";
        for(auto b : a[i-1]) {
            int ml = get<0>(b), xl = get<1>(b), yl = get<2>(b);
            int mnew = ml + 2 * i - 1;
            int xnew = xl + ml + i * (2 * i - 1);
            int ynew = yl + 3 * ml + 3 * i - 2;
            if(tiii(mnew, xnew, ynew) == p) {
                return "(" + solve(i - 1, b) + ")";
            }
        }
        for(int j=1;j<i;j++) {
            for(auto b1 : a[j]) for(auto b2 : a[i-j]) {
                int ml = get<0>(b1), mr = get<0>(b2);
                int xl = get<1>(b1), xr = get<1>(b2);
                int yl = get<2>(b1), yr = get<2>(b2);
                int mnew = ml + mr;
                int xnew = xl + xr + 2 * j * mr;
                int ynew = yl + yr;
                if(tiii(mnew, xnew, ynew) == p) {
                    return solve(j, b1) + solve(i - j, b2);
                }
            }
        }
        assert(0);
    }
    
    void add(int i, int mm, int xx, int yy) {
        long long v = mm * (1ll << 40ll) + xx * (1ll << 20ll) + yy;
        if(HASH.insert(v)) a[i].push_back(tiii(mm, xx, yy));
    }
    
    int main() {
    #ifdef LOCAL
        freopen("E.in", "r", stdin);
        // freopen("E.out", "w", stdout);
    #endif
        clock_t aa = clock();
        a[0].push_back(tiii(0, 0, 0));
        for(int i=1;i<=18;i++) {
            HASH.init();
            for(int j=1;j<i;j++) { // the size of left bracket sequence.
                for(auto b1 : a[j]) for(auto b2 : a[i-j]) {
                    int ml = get<0>(b1), mr = get<0>(b2);
                    int xl = get<1>(b1), xr = get<1>(b2);
                    int yl = get<2>(b1), yr = get<2>(b2);
                    int mnew = ml + mr;
                    int xnew = xl + xr + 2 * j * mr;
                    int ynew = yl + yr;
                    add(i, mnew, xnew, ynew);
                }
                cnt += a[j].size() * a[i-j].size();
            }
            // ( sequence )
            for(auto b : a[i-1]) {
                int ml = get<0>(b), xl = get<1>(b), yl = get<2>(b);
                int mnew = ml + 2 * i - 1;
                int xnew = xl + ml + i * (2 * i - 1);
                int ynew = yl + 3 * ml + 3 * i - 2;
                add(i, mnew, xnew, ynew);
            }
            cnt += a[i-1].size();
            dbg1(i); dbg1(a[i].size()); dbg2(cnt);
        }
        clock_t b = clock();
        // cerr << "running " << b - aa << "ms
    ";
        scanf("%d%lf%lf", &n, &x, &y);
        n /= 2;
        for(auto b : a[n]) {
            int bm = get<0>(b), bx = get<1>(b), by = get<2>(b);
            double xx = 1.0 * bx / bm, yy = 1.0 / 3.0 * by / bm;
            if(fabs(xx - x) + fabs(yy - y) <= 1e-8) {
                printf("%s
    ", solve(n, b).c_str());
                return 0;
            }
        }
        return 0;
    }
    

    I. Is It Rated?

    题意:略

    做法:神奇的做法。算法名称叫做 Weighted Majority Algorithm,也可以百度天气预测问题。但是百度到的证明的数学符号似乎都崩了,所以我看不懂这算法是咋证明的……

    代码实现中是给每一个人赋值一个他这一轮的可信权值,然后对 n 个人遍历一遍得出这一轮选 0 的概率。然后再整一个 ([0,1]) 的随机数去跟上面的概率比对选 0/1.

    这一轮结束后,如果对了那么他的可信权值不用管,否则他的可信权值乘一个系数 (alpha)。题解中说 (frac{1}{2} < alpha < frac{19}{20})。代码中取的 0.8。

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 1000 + 5;
    
    int n, m;
    
    char s[N];
    long double a[N];
    
    int main() {
        srand(time(0));
        scanf("%d%d", &n, &m);
        for(int i=1;i<=n;i++) a[i] = 1e18;
        for(int i=1;i<=m;i++) {
            scanf("%s", s + 1); getchar();
            long double tot0 = 0;
            long double tot = 0;
            for(int j=1;j<=n;j++) {
                if(s[j] == '0') tot0 += a[j];
                tot += a[j];
            }
            
            if(rand() * tot < tot0 * RAND_MAX) putchar('0');
            else putchar('1');
            putchar(10);
            fflush(stdout);
    
            char ans; ans = getchar(); 
            for(int j=1;j<=n;j++) if(s[j] != ans) {
                a[j] *= 0.8;
            }
        }
        return 0;
    }
    

    J. Japanese Game

    题意:略

    做法:

    假如说现在有 profile 了,怎么弄出来 mask?

    显然可以得出按照最左边放置的放法,然后再得出按照最右边放置的放法,然后看看哪些一直都是 # 的方块仍然还是属于一个大 # 块的,这些方块在 mask 中仍然是 #。

    考虑 (O(n^2)) 做法。枚举按照最左边放置的放法,在 mask 中显示 # 的格子需要向左移动 (i) 格,而小于等于 (i) 格的 # 全部都被删掉了需要加回来。这样只需要分开考虑每一个 _ 块怎么填即可。

    • 如果 _ 在边上且长度为1,无解。
    • 如果 __ 在中间且长度为 2,无解。
    • 对每一个 _ 块加入长度为 1 或 2 的 # 块,一边加一边判断。

    可以发现,若在 (i geq 4) 时候有答案成立,一定有 (i' = i - 2) 有答案成立。那么时间复杂度 (O(n))

    #include <bits/stdc++.h>
    using namespace std;
    
    #define pb push_back
    #define mk make_pair
    #define dbg1(x) cout << #x << " = " << x << ", "
    #define dbg2(x) cout << #x << " = " << x << endl
    
    const int N = 100000 + 5;
    const int inf = 1e9 + 7;
    
    int n;
    char ss[N];
    char s[N];
    
    struct Node {
        int l;
        char ch;
        Node(int l=0, char ch=''):l(l), ch(ch) {}
    };
    
    bool check(int lift) {
        for(int i=1;i<=n;i++) s[i] = ss[i];
        for(int i=1;i<=n;i++) if(s[i] == '#') {
            for(int j=1;j<=lift;j++) s[i-j] = '#';
        }
        s[n-lift+1] = '';
        vector<Node> a;
        for(int i=1, j=1; i<=n-lift; i++) {
            if(s[i] == s[j] && s[i+1] != s[i]) {
                int len;
                if(s[j] == '_') {
                    if(i == j) { 
                        if(j == 1 || i == n - lift) return false;
                        else { j = i + 1; continue;}
                    }
                    if((j != 1 && i != n - lift) && i - j + 1 == 2) return false;
                    len = i - j + (j == 1) + (i == n - lift);
                }
                else len = i - j + 1;
                a.pb(Node(len, s[j]));
                j = i + 1;
            }
        }
        vector<int> ans;
        for(int i=0;i<a.size();i++) {
            if(a[i].ch == '#') { ans.push_back(a[i].l); continue;}
            int len = a[i].l;
            for(int j=0;j<len/2-1;j++) {
                ans.pb(1);
                if(lift < 1) return false;
            }
            if(len % 2 == 1) {
                ans.pb(2);
                if(lift < 2) return false;
            }
            else { 
                ans.pb(1); 
                if(lift < 1) return false;
            }
        }
        printf("%d
    ", (int)ans.size());
        for(int i=0;i<ans.size();i++) {
            printf("%d%c", ans[i], " 
    "[i == (int)ans.size()-1]);
        }
        return true;
    }
    
    int main() {
    #ifdef LOCAL
        freopen("J.in", "r", stdin);
    #endif
        scanf("%s", ss + 1);
        n = strlen(ss + 1);
        int lift = inf;
        if(ss[1] == '#' || ss[n] == '#') lift = 0;
        for(int i=1, j=1;i<=n;i++) {
            if(ss[j] == '_' && ss[i] == '_' && i == n) {
                lift = min(lift, i - j + 1);
            }
            else if(ss[j] == '_' && ss[i] == '#') {
                if(j == 1) lift = min(lift, i - j);
                else lift = min(lift, i - j - 1);
            }
            if(ss[j] != ss[i]) j = i;
        }
    
        for(int k=0;k<=min(lift, 3); k++) {
            if(check(k)) return 0;
        }
        puts("-1");
        return 0;
    }
    
  • 相关阅读:
    line-height:150%和line-height:1.5的区别
    javascript: with 表单验证
    CSS实现背景透明,文字不透明,兼容所有浏览器
    关于伪类元素:before和:after
    图片预览实例分享
    微信浏览器取消缓存的方法
    学习笔记(四):jQuery之动画效果
    学习笔记(三):jQuery之DOM
    Git 常用命令
    开发规范(三)数据库 By 阿里
  • 原文地址:https://www.cnblogs.com/bringlu/p/14898633.html
Copyright © 2020-2023  润新知