zoukankan      html  css  js  c++  java
  • 「Codeforces 79D」Password

    Description

    有一个 01 序列 (a_1,a_2,cdots,a_n),初始时全为 (0)

    给定 (m) 个长度,分别为 (l_1sim l_m)

    每次可以选择一个长度为某个 (l_i) 区间,对其进行翻转操作((0 o 1,1 o 0))。

    求最少的操作次数,使得最后有且仅有 (k) 个位置为 (1)(k) 个位置给定),其余为 (0)

    (1leq nleq 10^4,1leq kleq 10,1leq mleq 100)

    Solution

    原问题等价于:

    给定 01 序列 (a_1,a_2,cdots,a_n),有 (k) 个位置为 (1),其余为 (0)。每次可以翻转长度为 (l_i) 的区间,求将 (a) 清零的最小操作数。

    操作为区间修改,考虑差分。

    由于是“区间取反”,一般的作差差分无法使用,考虑 异或差分。令 (b_i=a_i ext{ xor }a_{i+1})(设 (a_0=a_{n+1}=0))。

    那么,将原序列中的区间 ([l,r]) 翻转,等价于将差分序列中的 (b_{l-1},b_r) 取反(其他元素不变)。

    Step1

    考虑到 (b) 序列初始最多只有 (2k)(1),则问题转化为:

    给定 01 序列 (b_0,b_2,cdots,b_n),最多有 (2k) 个位置为 (1)。每次可以选择一对距离为 (l_i) 的位置,将其取反。求将 (b) 清零的最小操作次数。

    设选择的一对位置为 ((x,y))分类讨论:

    • (b_x=0,b_y=0),则操作后 (b_x=1,b_y=1),增加 (2)(1)。(显然会使答案更劣,不会发生)

    • (b_x=1,b_y=1),则操作后 (b_x=0,b_y=0),相当于 (2)(1) 碰撞变成 (0),减少 (2)(1)

    • (b_x=1,b_y=0),则操作后 (b_x=0,b_y=1),相当于把 (x) 上的 (1) 移到 (y)(1) 的数量不变。

    • (b_x=0,b_y=1),与 (b_x=1,b_y=0) 同理,(1) 的数量不变。

    Step2

    问题等价于:(第 (i) 个节点有标记相当于 (b_i=1)

    给定一个有 (n+1) 个节点的图(点的编号为 (0sim n))。当 (dis(x,y)=l_i) 时,存在边 ((x,y))。初始时最多有 (2k) 个节点上有标记,每次可以沿边移动标记。两个标记相遇就会消失。求使所有标记消失的最少移动次数。

    设标记点分别为 (p_0,p_1,cdots,p_{g-1})

    首先,我们可以通过 BFS 计算出所有标记点对之间的距离。

    (2kleq 20),考虑 状压 DP(差分序列中为 (0) 的位置不用管,只考虑 (2k)(1),有 (2^{2k}) 种状态)。令 (f_S) 表示标记点状态为 (S) 时使所有标记消失的最少移动次数。

    (S) 二进制下的第 (i) 位为 (1) 表示标记点 (p_i) 上的标记未消失。显然转移只需考虑 (2)(1) 碰撞变成 (0) 的情况,其他情况都是没有意义的,所以我们不需要考虑非初始标记点的状态)

    转移:(S) 二进制下为 (1) 的其中两个位为 (i,j)(f_{S}=min{f_{S-2^i-2^j}+dis(i,j)})

    初始时 (f_0=0,f_i=infty\,(i eq 0))。答案即为 (f_{2^g-1})

    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    const int N=1e4+5,M=22;
    int n,m,k,x,a[N],b[N],l[N],g,c[N],d[N],dis[M][M],f[1<<M],ans;
    bool v[N];
    queue<int>q;
    void bfs(int s){    //BFS 计算出所有标记点对之间的距离 
        for(int i=0;i<=n;i++) d[i]=1e18,v[i]=0;
        d[s]=0,v[s]=1,q.push(s);
        while(q.size()){
            int x=q.front(),y;q.pop();
            for(int i=1;i<=m;i++){
                if((y=x+l[i])<=n&&!v[y]) d[y]=d[x]+1,v[y]=1,q.push(y);
                if((y=x-l[i])>=0&&!v[y]) d[y]=d[x]+1,v[y]=1,q.push(y);
            } 
        }
        for(int i=0;i<=n;i++)
            if(b[i]) dis[c[s]][c[i]]=d[i];
    }
    signed main(){
        scanf("%lld%lld%lld",&n,&k,&m);
        for(int i=1;i<=k;i++)
            scanf("%lld",&x),a[x]=1;
        for(int i=1;i<=m;i++)
            scanf("%lld",&l[i]);
        for(int i=0;i<=n;i++)
            b[i]=a[i]^a[i+1],c[i]=(b[i]?g++:0);    //b 为差分序列。若节点 i 是标号点,也就是 b[i]=1,则节点 i 对应的标记点编号为 c[i](编号从 0 开始) 
        for(int i=0;i<=n;i++)
            if(b[i]) bfs(i);    //注意这里是 if(b[i]) 而不是 if(c[i]),因为标记点的编号是从 0 开始的 
        for(int s=1;s<(1<<g);s++){     //状压 DP 
            f[s]=1e18;
            for(int i=0;i<g;i++){
                if(!((s>>i)&1)) continue;
                for(int j=i+1;j<g;j++)    //枚举 S 二进制下为 1 的两个位为 i,j 
                    if((s>>j)&1) f[s]=min(f[s],f[s-(1<<i)-(1<<j)]+dis[i][j]);
            } 
        } 
        ans=f[(1<<g)-1],printf("%lld
    ",ans==1e18?-1:ans);
        return 0;
    }
    转载请注明原文链接
  • 相关阅读:
    level trigger 与 edge trigger 的区别
    使用ifstream时碰到的一个小问题
    转一篇 sed one line
    select(poll)效率,与异步网络IO,AIO, libevent, epoll
    类的成员函数指针的使用
    awk 的OFS使用 小 tips
    一句话打通所有机器,小脚本
    usleep sleep函数会重置clock 的返回值
    qstore 的 chunk重构小记
    判断质数的方法
  • 原文地址:https://www.cnblogs.com/maoyiting/p/14408748.html
Copyright © 2011-2022 走看看