状压dp

  啃了几天状压dp终于觉得可以了,其实状压很简单的,关键是不好想状态转移,然后就是好几道例题,其中有一道是本人自己想的成就感满满的。

这道题是入门吧关键是学会一些位运算的操作思路很简单bfs宽搜即可,还有状态的携带,把状态压成数字进行搜索。

#include<bits/stdc++.h>
#include<iomanip>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cstdlib>
#include<vector>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<utility>
#include<cctype>
#include<algorithm>
#include<set>
#include<bitset>
#include<cmath>
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void put(int x)
{
    x<0?x=-x,putchar('-'):0;
    int num=0;char ch[50];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('
');return;
}
const int maxn=102;
int n,m;
int a[maxn][maxn];
queue<pair<int,int> > q;
int vis[100002];
void bfs()
{
    q.push(make_pair(0,(1<<n)-1));
    while(q.size()!=0)
    {
        int v=q.front().first;
        int tn=q.front().second;
        q.pop();
        vis[tn]=1;
        if(tn==0){put(v);return;}
        for(int i=1;i<=m;i++)
        {
            int now=tn;
            for(int j=1;j<=n;j++)
            {
                int x=now;
                if(a[i][j]==1)
                {
                    x=x>>(j-1);
                    if((x&1)==1)now=now-(1<<(j-1));
                }
                if(a[i][j]==-1)
                {
                    x=x>>(j-1);
                    if((x&1)==0)now=now|(1<<(j-1));
                }
            }
            if(vis[now]==1)continue;
            q.push(make_pair(v+1,now));
        }
    }
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=m;i++)
        for(int j=1;j<=n;j++)a[i][j]=read();
    bfs();
    if(vis[0]==0)put(-1);
    return 0;
}
View Code

对于这道题就有点动态规划的味道了,我们必须要设状态了,这个当然是很好设的。

f[i][j]表示第i行第j个状态所能得到的最大值最后累加即可,至于合法状态可以先预处理出来。

#include<bits/stdc++.h>
#include<iomanip>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cstdlib>
#include<vector>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<utility>
#include<cctype>
#include<algorithm>
#include<set>
#include<bitset>
#include<cmath>
#define up(p,n,i) for(int i=p;i<=n;i++)
#define mod 100000000
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void put(int x)
{
    x<0?x=-x,putchar('-'):0;
    int num=0;char ch[50];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('
');return;
}
const int MAXN=5000,maxn=20;
int n,m;
int a[maxn][maxn],f[maxn][MAXN];//f[i][j]表示第i行的第j个状态所能得到的方案数,purpose:∑f[n][i](i<=m)
int q[maxn][MAXN],flag[maxn],t=0,ans=0;
void prepare(int i,int x)
{
    t=0;
    for(int j=0;j<=(1<<m)-1;j++)
    {
        //if(j==5)cout<<((j&x)!=0)<<endl;
        if(((j&(j<<1))!=0)||((j&(j>>1))!=0)||((j&x)!=0))continue;
        q[i][++t]=j;
    }
    flag[i]=t;
    return;
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    up(1,n,i)
    {
        int cnt=0;//生成当前这行的状态
        up(1,m,j)
        {
            a[i][j]=read();
            cnt=(cnt<<1)+1-a[i][j];
        }
        //cout<<cnt<<endl;
        prepare(i,cnt);
    }
    //up(1,n,i)cout<<flag[i]<<endl;
    memset(f,0,sizeof(f));
    for(int i=1;i<=flag[1];i++)f[1][i]=1;
    up(2,n,i)
    {
        up(1,flag[i],j)
        {
            up(1,flag[i-1],k)
            {
                if((q[i][j]&q[i-1][k])!=0)continue;
                f[i][j]=(f[i][j]+f[i-1][k])%mod;
            }
        }
    }
    up(1,flag[n],i)ans=(ans+f[n][i])%mod;
    put(ans);
    return 0;
}
View Code

真正的状压dp了,但是很简单,也是先预处理出第一行(也就是每一行的状态)承接一下即可。

#include<bits/stdc++.h>
#include<iomanip>
#include<iostream>
#include<cstdio>
#include<ctime>
#include<cmath>
#include<cstdlib>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<vector>
#include<cctype>
#include<utility>
#include<algorithm>
#include<string>
#include<cstring>
using namespace std;
inline long long read()
{
    long long x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void put(long long x)
{
    x<0?x=-x,putchar('-'):0;
    long long num=0;char ch[50];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('
');return;
}
const long long MAXN=200;
long long n,k,sum=0;
long long flag[MAXN],ans=0,q[MAXN];
long long f[10][MAXN][MAXN];
//f[i][k][j]表示第i行放k个国王的状态是j的方案数
//状态数s[1]=2;s[2]=3;s[3]=5   ...s[i]=s[i-1]+s[i-2];
void getstate()
{
    for(long long i=0;i<=(1<<n)-1;i++)
    {
        if(i&(i<<1))continue;
        if(i&(i>>1))continue;
        long long x=i,cnt=0;
        while(x)
        {
            if(x&1)cnt++;
            x=x>>1;
        }
        if(cnt>k)continue;
        q[++sum]=i;
        flag[sum]=cnt;
    }
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();
    k=read();
    getstate();
    for(long long i=1;i<=sum;i++)f[1][flag[i]][i]=1;
    for(long long i=2;i<=n;i++)
    {
        for(long long j=1;j<=sum;j++)
        {
            for(long long u=0;u<=k;u++)
            {
                if(f[i-1][u][j]==0)continue;
                for(long long t=1;t<=sum;t++)
                {
                    if(flag[t]+u>k)continue;
                    if(q[t]&q[j])continue;
                    if(q[t]&(q[j]<<1))continue;
                    if(q[t]&(q[j]>>1))continue;
                    f[i][flag[t]+u][t]+=f[i-1][u][j];
                }
            }
        }
    }
    for(long long i=1;i<=sum;i++)ans+=f[n][k][i];
    put(ans);
    return 0;
}
View Code

这个就体现出难度了,很难想的知道状态压缩但是和前面的都不太一样所以,可以有一个特殊的状态f[i][j][k]表示第i行是第j个状态i-1行是第k个状态。

然后转移则是由f[i-1][k][l]转移过来因为当前这个地方放炮兵一下子就影响2行。

#include<bits/stdc++.h>
#include<iomanip>
#include<iostream>
#include<cstdio>
#include<ctime>
#include<cmath>
#include<cstdlib>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<vector>
#include<cctype>
#include<utility>
#include<algorithm>
#include<string>
#include<cstring>
#define INF 2147483646
using namespace std;
inline long long read()
{
    long long x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void put(long long x)
{
    x<0?x=-x,putchar('-'):0;
    long long num=0;char ch[50];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('
');return;
}
const int MAXN=11;
int n,m,cnt=0,ans=0;
char a[102][102];
int f[102][102][102];//每一行最多状态不超过100;
//f[i][j][k]表示第i行的第j个状态和第i-1行的k状态包容所能安放的最多炮兵数量。
//所以有状态转移:f[i][j][k]=max(f[i-1][k]][l]+count(j));题目要求下面两行也不能放
int v[102][102];//v[i][j]表示第i行第j个的状态
int flag[102][102];//flag[i][j]表示第i行第j个的状态所有的炮兵数量
int record[102];//vt 记录 记载 n档案 履历 唱片 最高纪录 vi记录 录音 adj 创纪录的
void getstate(int x)
{
    int tmp=0,t=0;
    for(int i=1;i<=m;i++)
    {
        tmp<<=1;
        if(a[x][i]=='H')tmp|=1;
    }
    for(int i=0;i<1<<m;i++)
    {
        if((i&(i<<1))||(i&(i>>1))||(i&(i<<2))||(i&(i>>2)))continue;
        if(tmp&i)continue;
        v[x][++t]=i;
        int u=i,sum=0;
        while(u)
        {
            if(u&1)sum++;
            u=u>>1;
        }
        flag[x][t]=sum;
    }
    record[x]=t;
    return;
}

int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=n;i++)
    {
        scanf("%s",a[i]+1);
        getstate(i);
    }
    //for(int i=1;i<=n;i++)put(record[i]);
    //for(int i=1;i<=record[1];i++)put(flag[1][i]);
    //for(int i=1;i<=record[1];i++)put(v[1][i]);
    memset(f,0xcf,sizeof(f));
    f[0][0][0]=1;record[0]=1;
    for(int i=1;i<=record[1];i++)f[1][i][1]=flag[1][i];
    for(int i=2;i<=n;i++)
    {
        for(int l=1;l<=record[i-2];l++)
            for(int k=1;k<=record[i-1];k++)
            {
                if(v[i-1][k]&v[i-2][l])continue;
                for(int j=1;j<=record[i];j++)
                {
                    if(v[i][j]&v[i-1][k])continue;
                    if(v[i][j]&v[i-2][l])continue;
                    f[i][j][k]=max(f[i][j][k],f[i-1][k][l]+flag[i][j]);
                    ans=max(ans,f[i][j][k]);
                }
            }
    }
    put(ans);
    return 0;
}
View Code

当然此题还是可以用3进制进行状态压缩2代表当前放炮兵,1代表缓冲区,0也代表缓冲区。

但是这个相当难写,有较快效率的是dfs类型的搜索不能再提前预处理出状态了,因为那样做不值得而且非常难写且效率不高,如果由第0行生成第1行状态的话,这样的效率应该会更高。

//#include<bits/stdc++.h>
#include<iomanip>
#include<iostream>
#include<cstdio>
#include<cmath>
#include<ctime>
#include<cstdlib>
#include<cstring>
#include<string>
#include<set>
#include<bitset>
#include<queue>
#include<deque>
#include<stack>
#include<cctype>
#include<utility>
#include<algorithm>
#include<map>
#include<vector>
using namespace std;
inline long long read()
{
    long long x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void put(long long x)
{
    x<0?x=-x,putchar('-'):0;
    long long num=0;char ch[50];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar(' ');return;
}
const int MAXN=102,maxn=60000;
int n,m;
int p[12]={0,1,3,9,27,81,243,729,2187,6561,19683,59049};
char a[MAXN][MAXN];
int v[15],ans=0;
int f[MAXN][maxn];
//f[i][j]表示第i行第j个状态所能获得的最多炮兵数量。
void getstate(int x,int cnt)//利用上一行状态更新当前状态
{
    for(int i=1,u=cnt;i<=m;i++,u/=3)
    {
        if(u%3==2)v[i]=1;
        else if(u%3==1)v[i]=0;
        else if(a[x][i]=='H')v[i]=0;
        else v[i]=2;
    }
}
void dfs(int x,int t,int last,int now,int cnt)
{
    if(t>m)
    {
        f[x+1][now]=max(f[x+1][now],f[x][last]+cnt);
        return;
    }
    if(v[t]==2)
    {
        int v1=v[t+1],v2=v[t+2];
        if(v1==2)v[t+1]=0;
        if(v2==2)v[t+2]=0;
        dfs(x,t+1,last,now+2*p[t],cnt+1);
        v[t+1]=v1;v[t+2]=v2;
    }
    if(v[t]==1)dfs(x,t+1,last,now+p[t],cnt);
    if(v[t]==2||v[t]==0)dfs(x,t+1,last,now,cnt);
    return;
    
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=n;i++)scanf("%s",a[i]+1);
    memset(f,-1,sizeof(f));
    f[0][0]=0;
    //getstate(1,0);
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<p[11];j++)
        {    
            if(f[i][j]<0)continue;
            getstate(i+1,j);
            dfs(i,1,j,0,0);
        }
    }
    for(int j=0;j<p[11];j++)ans=max(ans,f[n][j]);
    put(ans);
    return 0;
}
View Code

更难以理解。当然。

这道题的话也还状态压缩只不过竖着的砖块需要用1来表示。然后1必须让0承接。这样的话就可以实现状态转移了。

#include<bits/stdc++.h>
#include<iomanip>
#include<iostream>
#include<cstdio>
#include<ctime>
#include<cmath>
#include<cstdlib>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<vector>
#include<cctype>
#include<utility>
#include<algorithm>
#include<string>
#include<cstring>
using namespace std;
inline long long read()
{
    long long x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void put(long long x)
{
    x<0?x=-x,putchar('-'):0;
    long long num=0;char ch[50];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('
');return;
}
const long long MAXN=12;
long long n,m;
long long f[MAXN][1<<11],q[1<<11],t=0;//q存状态
//f[i][j]表示第i行以j状态为结尾的状态数。
//只存状态是是不行的,因为第一行不合法的状态不一定到最后一行还不合法。
//如1 1 1 下一行 0 0 0用的了不合法的状态但是最后的状态仍是合法的所以需要枚举所有状态。
//而不是只单单枚举那些和合法的状态。
void getstate()
{
    for(long long i=0;i<1<<m;i++)
    {
        long long flag=0,cnt=0;
        for(long long j=0;j<m;j++)
        {
            if(i>>j&1)flag|=cnt,cnt=0;
            else cnt^=1;
        }
        q[i]=flag|cnt?0:1;
    }
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    getstate();
    memset(f,0,sizeof(f));
    //for(long long i=1;i<=t;i++)put(q[i]);
    f[0][0]=1;
    for(long long i=1;i<=n;i++)
    {
        for(long long j=0;j<1<<m;j++)
        {
            for(long long k=0;k<1<<m;k++)
            {
                if(f[i-1][k]==0)continue;
                if(j&k)continue;
                if(q[j|k]==0)continue;
//想了2h对本道题的深刻理解,上个状态有1的地方必须补0这个状态可能不是合法的
//因为可能是要承接上一个的状态所以不需要一定合法,和上一个状态保存吻合即可。
                f[i][j]+=f[i-1][k];
            }
        }
    }
    put(f[n][0]);
    return 0;
}
View Code

值得再次复习呢。

加油

原文地址:https://www.cnblogs.com/chdy/p/10251883.html