BZOJ3003 差分+状压

来源:互联网 发布:凡科网站seo 编辑:程序博客网 时间:2024/06/15 05:53

给定一个长度为n的01序列以及L个整数a[i]。每次可以选择连续的长度为a[i]的一段取反,给出末状态。求最少操作的次数。

首先,这类选择一段区间操作的题可以把序列差分。于是我们把给出的末状态差分,然后考虑把每次操作也差分,于是每次操作就是选择两个位置差为a[i]的数对取反。

然后我们对所有的a[i]跑一遍背包(即取正也取负),这样就得到了把任意位置差的两个点取反的最小代价。

接着考虑把所有的位置给状压,然后每次取两个数对。但是这样是2nn2的,不是很优雅。然后发现我们每次可以固定取一个 lowbit,这样一定是可以涵盖到所有的方案

#include<bits/stdc++.h>#define inf ~0u>>2using namespace std;const int N=2e4+5;inline int read(){    int x=0;char ch=getchar();    while(ch<'0'||ch>'9'){ch=getchar();}    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}    return x;}int T,n,k,l; int a[N],c[N],pos[N],f[N],g[(1<<22)];int cnt; queue<int>q;int main(){#ifdef Devil_Gary    freopen("in.txt","r",stdin);#endif    T=read();    while(T--){        cnt=0,n=read(),k=read(),l=read();        for(int i=1;i<=k;i++) a[i]=read();        sort(a+1,a+k+1);        k=unique(a+1,a+k+1)-(a+1);        a[0]=-1,a[k+1]=n+2;        for(int i=1;i<=k;i++) {            if(a[i]!=a[i-1]+1) pos[cnt++]=a[i];            if(a[i]!=a[i+1]-1) pos[cnt++]=a[i]+1;         }        sort(pos,pos+cnt);        for(int i=1;i<=n;i++) f[i]=inf;        for(int i=1;i<=l;i++) c[i]=read();        for(int i=1;i<=l;i++) f[c[i]]=1,q.push(c[i]);        while(!q.empty()){            int o=q.front();q.pop();            for(int i=1;i<=l;i++){                if(o+c[i]<=n&&f[o+c[i]]==inf){                    f[o+c[i]]=f[o]+1;                    q.push(o+c[i]);                }                if(o-c[i]>=1&&f[o-c[i]]==inf){                    f[o-c[i]]=f[o]+1;                    q.push(o-c[i]);                }            }         }        for(int s=1,o;s<(1<<cnt);s++){            o=0,g[s]=inf;            for(int i=0;i<cnt;i++)                if(s&(1<<i)){                    o=i;break;                }            for(int i=o+1;i<cnt;i++)                if(s&(1<<i))                    g[s]=min(g[s],g[s^(1<<i)^(1<<o)]+f[pos[i]-pos[o]]);         }        printf("%d\n",g[(1<<cnt)-1]==inf?-1:g[(1<<cnt)-1]);    }}
原创粉丝点击