zoukankan      html  css  js  c++  java
  • NERC2021 B. Button Lock

    题目链接

    NERC2021 B. Button Lock

    题意

    你面前有一个数字锁,上面有 (d) 个按键,你按一下,按键就会被按下去,另外还有一个 Reset 键,按了以后之前所有被按下的键都会弹起来,当你按出了正确的密码,则锁会立刻打开。

    现在你知道有 (n) 个可能的密码,请问至少需要按多少次按钮才能把所有密码都试一遍(按 Reset 也算一次)。

    (1leq dleq 10)(1leq nleq 2^d-1)

    思路

    建个图,如果密码 (i) 能通过另外按下一些键得到密码 (j),则连一条 (i ightarrow j) 的有向边,可以发现原题转化成了类似于有向图最小可重复点路径覆盖的问题,不同之处在于这里每条路径是带权的,权值为路径终点代表的那个密码包含的按下的键个数(密码的输入形式是 (01) 串,下来就直接说是 (1) 的个数了)。

    这个看起来是能贪心的,先考虑跑匈牙利,首先最优解肯定是拆点二分图的一个最大匹配,那我们对点按照 (1) 的个数排序,从大到小一个个匹配,由于若一个点能被匹配,那它必然存在于最优解的方案中,从而这么做是正确的。

    但是 (O(NM)) 的时间复杂度看起来不大能过,想想用 (Dinic) 网络流怎么做,因为 (Dinic) 一次是匹配多条增广路,然后再更新残余网络,所以原本我们需要的最佳顺序可能会被打乱,而最佳顺序跑不出新的增广路,于是不会更新方案,事实上你会在 (CF) 上 Wrong answer on test 23。

    既然这样不正确,经过一定的思考(手玩对拍的数据)能够发现,其实我们直接强制匹配顺序就可以了,先根据 (1) 的数量对点进行分层,之后从大往小一层层地加入点,每加一层跑一遍 (Dicnic),层之间的点对答案的贡献没有区别,从而这道题就被解决了。

    分析一下复杂度,边(即子集关系)的数量级是 (O(3^d)) 的,而 (n)(O(2^d)) 级别的,所以时间复杂度是 (O(3^d2^frac{d}{2})),同时还可以发现,原来跑匈牙利的时间复杂度只有 (O(6^d)),已经足够通过本题了。

    Code

    我是傻子,没发现匈牙利能过,调半天写了 (Dinic),不过速度确实比 (Hungary) 要快一点。

    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #include<queue>
    #include<vector>
    #define mem(a,b) memset(a,b,sizeof(a))
    #define rep(i,a,b) for(int i = (a); i <= (b); i++)
    #define per(i,b,a) for(int i = (b); i >= (a); i--)
    #define N 2100
    #define M 210000
    #define Inf 0x3f3f3f3f
    using namespace std;
    int n, d;
    int num[N];
    int head[N], to[M], nxt[M];
    int cap[M], now[N], dis[N], pre[N];
    int cnt;
    queue<int> q;
    vector<int> layers[12];
    void init(){mem(head, -1), cnt = -1;}
    void add_e(int a, int b){
        nxt[++cnt] = head[a], head[a] = cnt, to[cnt] = b;
        nxt[++cnt] = head[b], head[b] = cnt, to[cnt] = a;
        cap[cnt] = 0, cap[cnt^1] = 1;
    }
    bool bfs(){
        mem(dis, 0);
        q.push(0), dis[0] = 1;
        while(q.size()){
            int cur = q.front(); q.pop();
            now[cur] = head[cur];
            for(int i = head[cur]; ~i; i = nxt[i]){
                if(cap[i] && !dis[to[i]]){
                    q.push(to[i]);
                    dis[to[i]] = dis[cur]+1;
                }
            }
        }
        return dis[2*n+1];
    }
    int dfs(int x, int flow){
        if(x == 2*n+1) return flow;
        int flown = 0, i;
        for(i = now[x]; ~i; i=nxt[i]){
            now[x] = i;
            int y = to[i];
            if(cap[i] && dis[y] == dis[x]+1){
                int t = dfs(y, min(flow-flown, cap[i]));
                cap[i] -= t, cap[i^1] += t;
                if(t) pre[x] = y;
                flown += t;
                if(flown == flow) break;
            }
        }
        return flown;
    }
    int main(){
        ios::sync_with_stdio(false);
        cin>>d>>n;
        string s;
        rep(i,1,n){
            cin>>s;
            per(j,d-1,0) num[i] = (num[i]*2+(s[j] == '1'));
    	layers[__builtin_popcount(num[i])].push_back(i);
        }
        init();
        rep(i,1,n){
            add_e(0, i);
            add_e(i+n, 2*n+1);
        }
        per(stage,d,1){
    	rep(frt,stage+1,d){
                for(int x:layers[stage]) for(int y:layers[frt])
                if((num[x]&num[y]) == num[x]) add_e(x, y+n);
    	}
    	while(bfs()) while(dfs(0, Inf));
        }
        vector<int> ans;
        pre[0]=0;
        rep(stage,1,d) for(int i:layers[stage]){
            if(pre[i] == -1) continue;
            int t = i;
            ans.push_back(-1);
            rep(j,0,d-1) if(num[i]>>j&1) ans.push_back(j);
            while(pre[t]){
                int k = pre[t]-n;
                int diff = num[t]^num[k];
                rep(j,0,d-1) if(diff>>j&1) ans.push_back(j);
                pre[t] = -1;
                t = k;
            }
            pre[t] = -1;
        }
        cout<<(int)ans.size()-1<<endl;
        rep(i,1,(int)ans.size()-1){
            if(~ans[i]) cout<<ans[i]<<" ";
            else cout<<"R ";
        }
        cout<<endl;
        return 0; 
    }
    

    题外话:前两天码风被 (zjc) 神在线下吐槽,经过反省决定开始改成空格码风了。

  • 相关阅读:
    vux 数据模拟mockjs的使用
    vux 配置颜色问题
    vue-router 学习
    vue 学习笔记
    点击加载更多
    table td 固定宽度
    js scroll 滚动连续多次触发事件只执行一次
    Merge into的注意点之ORA-30926: 无法在源表中获得一组稳定的行?
    js页面中取值的注意点
    insert into的方式
  • 原文地址:https://www.cnblogs.com/Neal-lee/p/14635400.html
Copyright © 2011-2022 走看看