状压dp小结

今天模拟又考了一个SPFA+状压dp,巧了,我又不会,华丽丽爆零。(实际上我啥都不会)

状压dp即用二进制暴力枚举然后状态转移。用f[i][j]表示第i行在状态j的时候的方案数,其中j用一个二进制数来表示。转移的时候只要判断与当前行和上一行(或是上几行)是否冲突即可,如果不冲突,f[i][j]=∑f[i−1][q]其中q为不冲突的状态。∑1≤i≤cnt​f[n][i] 就是最后的答案,cnt为总状态数。状态转移前要先用dfs预处理一下方案的是否可行,是否冲突。
状压dp题的一个鲜明特征就是题目中的列数或者行数往往非常小,只有几或者十几,否则压缩不下。

1.luoguP3943星空

利用差分思想,设b[i]=a[i]^a[i-1],则b数组中最多有2k个1。对a的某一个区间进行取反时,b只有头尾被取反。将此问题转化为最短路,即有2k个起点,步长有m种,用spfa跑一遍最短路,使2k个1全部消去即可。为了避免重复计算,每次转移时可以先固定选择最小的位置,然后再枚举另一个位置。

#include<bits/stdc++.h>
#define maxn 40005
#define maxx 1000000009
using namespace std;
int n,k,m,x,y,d[maxn],have[maxn],w[23],val[maxn],f[500010],dis[23][23];
bool inq[maxn];
int read(){
    int s=0;char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
    return s;
}
queue<pair<int,int> > q;
int main(){
    n=read();k=read();m=read();
    for(int i=0;i<=n+1;i++) val[i]=1;
    for(int i=1;i<=k;i++) x=read(),val[x]=0;
    k=0;
    for(int i=1;i<=n+1;i++)
        if(val[i]^val[i-1]) w[++k]=i,have[i]=k;
    for(int i=1;i<=m;i++) d[i]=read(),d[i]++;
    int to;
    for(int i=1;i<=k;i++)
        for(int j=1;j<=k;j++)
            if(i!=j)  dis[i][j]=maxx;
    for(int i=1;i<=k;i++){
        for(int j=0;j<=n+1;j++) inq[j]=false;
        q.push(make_pair(w[i],0)); inq[w[i]]=true;
        while(!q.empty()){
            x=q.front().first;y=q.front().second;q.pop();
            for(int j=1;j<=m;j++){
                to=x+d[j]-1;
                if(to>n+1||inq[to]) continue;
                q.push(make_pair(to,y+1));
                inq[to]=true;
                if(have[to]) dis[i][have[to]]=y+1;
            }
            for(int j=1;j<=m;j++){
                to=x-d[j]+1;
                if(to<0||inq[to]) continue;
                q.push(make_pair(to,y+1));
                inq[to]=true;
                if(have[to]) dis[i][have[to]]=y+1;
            }
        }
    }
    for(int i=1;i<(1<<k);i++) f[i]=maxx;
    for(int i=0;i<(1<<k);i++){
        int j=0;
        while(i&(1<<j)) j++;
        for(int p=j+1;p<k;p++){
            if(!(i&(1<<p)))
                f[i|(1<<p)|(1<<j)]=min(f[i|(1<<p)|(1<<j)],f[i]+dis[j+1][p+1]);
        }
    }
//	if(f[(1<<k)-1]==maxx) printf("-1\n");
     printf("%d",f[(1<<k)-1]);
    
    return 0;
}

2.luoguP1879玉米田

跟上一题比起来这一题就简单多了,直接预处理然后表示状态好了。

#include<bits/stdc++.h>
using namespace std;
int dp[13][1<<12+5],f[15],n,m,a[15][15];
bool g[1<<12+5];
int read(){
    int s=0;char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
    return s;
}
int main(){
    m=read();n=read();
    for(int i=1;i<=m;i++)
        for(int j=1;j<=n;j++)
            a[i][j]=read(),f[i]=(f[i]<<1)+a[i][j];
    for(int i=0;i<(1<<n);i++)
        g[i]=((!(i&(i<<1)))&&(!(i&(i>>1))));
    dp[0][0]=1;
    for(int i=1;i<=m;i++){
        for(int j=0;j<(1<<n);j++){
            if(g[j]&&((j&f[i])==j)){
                for(int k=0;k<(1<<n);k++){
                    if(!(j&k)) dp[i][j]=(dp[i-1][k]+dp[i][j])%100000000;
                }
            }
        }
    }
    int ans=0;
    for(int i=0;i<(1<<n);i++) ans=(ans+dp[m][i])%100000000;
    printf("%d\n",ans);
}

3.luoguP2915奶牛混合起来

f[i][j]表示以i结尾,状态为j的方案数。(如,j=12,即1100,表示队中有3,4两头牛)
注意:要判断i是否在队中。

#include<bits/stdc++.h>
using namespace std;
long long n,k,a[18],y;
long long f[18][1<<18],ans;
long long read(){
    int x=0,f=1;char ch=getchar();
    while(ch>'9'||ch<'0'){if(ch=='-') f=-1; ch=getchar();}
    while(ch<='9'&&ch>='0') {x=x*10+ch-'0'; ch=getchar();}
    return x*f;
} 
int main(){
    n=read();y=read();
    for(int i=1;i<=n;i++) a[i]=read();
    for(int i=1;i<=n;i++)
        f[i][(1<<(i-1))]=1;//仅有i在队中,是混乱的,方案数为1
    for(int i=1;i<(1<<n);i++)
        for(int k=1;k<=n;k++)
            for(int j=1;j<=n;j++)
                if(abs(a[k]-a[j])>y&&(i&(1<<(k-1)))&&(i&(1<<(j-1)))==0)
                    f[j][i|(1<<(j-1))]+=f[k][i];
    for(int i=1;i<=n;i++)
        ans+=f[i][(1<<n)-1];
    printf("%lld",ans);
    return 0;
}

4.luoguP2704炮兵阵地

真恶心,,竟然上面两行对其都有影响……而且为了这题,我又不得不去学了下滚动数组……
f[i][j][k]表示当前考虑第k行,状态为j,上一行状态为i。

#include<bits/stdc++.h>
using namespace std;
int n,m,mapp[105],f[1<<10][1<<10][2],sum[1<<10],ans=0;
bool g[1<<10];
char ch;
int read(){
    int x=0,f=1;char ch=getchar();
    while(ch>'9'||ch<'0'){if(ch=='-') f=-1; ch=getchar();}
    while(ch<='9'&&ch>='0') {x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}
int getsum(int x){
    int tot=0;
    while(x){if(x&1) tot++; x=x>>1;}
    return tot;
}
int main(){
    n=read();m=read();
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            ch=getchar();
            while(ch!='P'&&ch!='H') ch=getchar();
            if(ch=='H') mapp[i]=mapp[i]<<1|1;
            else mapp[i]=mapp[i]<<1;
        }
    }//地图
    for(int i=0;i<(1<<m);i++) sum[i]=getsum(i);//预处理每种状态可以放几个
    for(int i=0;i<(1<<m);i++)
        for(int j=0;j<(1<<m);j++)
            if(!(i&j||i&mapp[0]||j&mapp[1]||(i&(i<<1))||(j&(j<<1))||(i&(i<<2))||(j&(j<<2))))
                f[i][j][1]=sum[i]+sum[j];//预处理第一行
    for(int i=2;i<n;i++){
        for(int j=0;j<(1<<m);j++){
            if((j&mapp[i-1])||(j&(j<<1))||(j&(j<<2))) continue;//上一行
            for(int k=0;k<(1<<m);k++){
                if((k&(k<<1))||(k&(k<<2))||(k&mapp[i])||(k&j)) continue;//本行
                for(int v=0;v<(1<<m);v++){
                    if((v&(v<<1))||(v&(v<<2))||(v&j)||(v&k)||(v&mapp[i-2])) continue;//上上行
                    f[j][k][i%2]=max(f[j][k][i%2],f[v][j][(i-1)%2]+sum[k]);
                }
            }
        }
    }
    for(int i=0;i<(1<<m);i++)
        for(int j=0;j<(1<<m);j++)
            ans=max(ans,f[i][j][(n-1)%2]);//咋都可以,只要在最后一行就行,取最大值。
    printf("%d",ans);
    return 0;
}

下面还会有的吧。。

猜你喜欢

转载自blog.csdn.net/qq_42324633/article/details/83476622