二进制的妙用——状态压缩

本文题目都是状压dp

洛谷P1879 [USACO06NOV]玉米田Corn Fields

首先来一道入门题目,题目大意是求一共有多少种种植方案?限制条件两个:一是只能在给定的一个图里面标记为1的那块田才能种草地;二是草地不能连续,即只能独立存在,上下左右都不能种草。

我们可以看到n m 比较小的时候基本就是要用状压来做了。

对于每一行的种草情况我们可以用二进制表示,比如一块长5×宽1的草地,如果草地状态是(11011),则表示只有第3块地没有种草。

那么我们可以看到符合情况的只能是类似这种状态(00000)(00001)(10101)因为题目要求不能连续嘛。

同时我们还要结合题目给定的那幅图里面可以哪块地可种,哪块地不能种,比如只有(1 0 1 0 1)能种,即只有第1 3 5 块地能种。所以如果枚举到(11000)类似这种情况时就不行,因为这个状态表示第2块地要种草,而题目要求表示第2块地不能种。

显而易见的我们可以用十进制来代表二进制来表示每种种植情况,那我们对于仅仅独立的一行,只需考虑草地是否相邻的同时看有没有破坏题目的要求。

而对于上下两行,我们也有可能两块草地相邻的对吧?这时候我们只能枚举两行各自的可行状态,然后看是否冲突相邻,不冲突就把情况累加到下一行,那么答案就是累加到最后一行了。

#include <bits/stdc++.h>
#define maxn (1<<13)
using namespace std;
int n,m,s[maxn],num[maxn][13],x,i,j,tot,op[13],f[13][maxn];
void solve()
{
    int i,j,k,q;
    for (i=0;i<(1<<m);i++)
    {
        if (i&(i<<1)) continue;
        s[++tot]=i;
/*        for (q=1;q<=n;q++)
        {
            k=0;
            for (j=0;j<m;j++)
                if (op[q]&(1<<j))
                {
                    if (i&(1<<j)) k++;
                }
                else {k=0;break;}
            num[tot][q]=k;
        }*/
    }
}
void dp()
{
    int i,j,t,sum=0;
    for (i=1;i<=tot;i++)
        if ((op[1]|s[i])==op[1]) f[1][i]=1;
    //for (i=1;i<=tot;i++) cout<<f[1][i]<<" ";
    //cout<<tot;
    for (i=2;i<=n;i++)
        for (j=1;j<=tot;j++)
                    for (t=1;t<=tot;t++)
                        if (!(s[t]&s[j]))
                            if ((s[j]|op[i])==op[i])
                                f[i][j]+=f[i-1][t];
//    for (i=1;i<=tot;i++) cout<<f[2][i]<<" ";
    for (j=1;j<=tot;j++) sum=(sum+f[n][j])% 100000000;
    printf("%d\n",sum);
}
int main()
{
    cin>>n>>m;
    for (i=1;i<=n;i++)
        for (j=1;j<=m;j++)
        {
            scanf("%d",&x);
            if (x) op[i]+=(1<<(m-j));
        }
    //cout<<op[2]<<endl;
    solve();
    dp();
    return 0;
 } 
View Code

这次代码风格有点飘,请见谅。

 

洛谷P4802 [CCO 2015]路短最

我们看到n还是很小,考虑状压。

还是很上一题一样,对于旅行情况我们可以用二进制表示,如状态是(11011),则表示只有第3个城市没有旅游过。

那么我们现在只有第3个城市要旅游,我们怎么知道他是怎么从哪个城市来到第3个城市的呢?

我们只能再加一维,表示最后去到的哪个城市。比如上面那种状态dp【(11011)2】【4】则表示我们旅行了1 2 4 5 城市,而且当前我们在第4个城市,而我们要去到第3个城市,就要从第4个城市去。

怎么实现这个过程呢?首先我们还是要先枚举状态,然后再枚举当前处于的那个城市,和没有去而且想要下一步去的城市。每次做这个过程时还是要用到一些巧妙的位操作,这里不再过多阐述。

#include <bits/stdc++.h>
#define maxn (1<<19)
using namespace std;
int x,y,n,v,m,first[20],next[maxn],zhi[maxn],value[maxn],i,j,k,u,tot,ans,f[maxn][20];
void add(int x,int y,int v)
{
    tot++;
    next[tot]=first[x];
    first[x]=tot;
    zhi[tot]=y;
    value[tot]=v;
}
int main()
{
    cin>>n>>m;
    memset(first,-1,sizeof(first));
    for (i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&v);
        add(x+1,y+1,v);
    }
    for (i=1;i<(1<<n);i++)
        for (j=1;j<=n;j++) f[i][j]=-0x3f3f3f3f;
    f[1][1]=0;
    for (i=1;i<(1<<n);i++)
    {
        for (j=1;j<=n;j++)
        {
            if (!(i&(1<<(j-1)))) continue;
            k=first[j];
            while (k!=-1)
            {
                u=zhi[k];
                
                if ((i&(1<<(u-1))))
                
                {
                    f[i][u]=max(f[i][u],f[i^(1<<(u-1))][j]+value[k]);
                }
                
                k=next[k];
            }
        }
    }
    for (i=1;i<(1<<n);i++) ans=max(ans,f[i][n]);
    printf("%d\n",ans);
    return 0;
}
View Code

 

洛谷P4772 灰化肥,会挥发

我们还是看到n很小,考虑状压。(以后看到n之类的数很小基本就是状态压缩的题目了)

和上一题一模一样,对于情况我们可以用二进制表示,如状态是(11011),则表示只有第3个字母没有去到。

首先我们要最短路预处理掉每个点之间的距离(好久没写最短路了......)

然后基本上就是上一题的做法了。

这里题目还需要输出步骤。我们定义一个数组表示当前dp的那个状态的权值总和。什么意思呢?题目要求我们要输出字典序最小的步骤,我们可以对每个字母给予一个权值,A是1,B是2.......。每当一个步骤加进来时,我们对当前权值和乘以16再加上字母的权值,类似于十六进制,如果你字典序大的话,每次累计出来的值肯定大(秦九韶算法?)。最好倒回去算就行了。

#include <bits/stdc++.h>
#define maxn 505
#define INF 0x3f3f3f3f
using namespace std;
struct node 
{
    int x,y;
};
node zuobiao[20];
const int dx[4]={0,1,0,-1};
const int dy[4]={1,0,-1,0};
int i,j,x,y,n,m,k,dis[maxn][maxn],vis[maxn][maxn],dp[(1<<17)][17],len[20][20];
char a[maxn][maxn];
long long state[(1<<17)][17];
void dijas(int p)
{
    int i,j,x,y;
    queue <pair<int,int> > q;
    for (i=1;i<=n;i++)
        for (j=1;j<=m;j++) 
        {
            dis[i][j]=INF;
            vis[i][j]=0;
        }
    dis[zuobiao[p].x][zuobiao[p].y]=0;
    q.push(make_pair(zuobiao[p].x,zuobiao[p].y));
    while (!q.empty())
    {
        x=q.front().first;
        y=q.front().second;
        q.pop();
        if (!vis[x][y])
        {
            vis[x][y]=1;
            for (i=0;i<=3;i++)
            {
                if (x+dx[i]>=1 && x+dx[i]<=n && y+dy[i]>=1 && y+dy[i]<=m)
                if (dis[x][y]+1<dis[x+dx[i]][y+dy[i]] && a[x+dx[i]][y+dy[i]]!='*')
                {
                    dis[x+dx[i]][y+dy[i]]=dis[x][y]+1;
                    q.push(make_pair(x+dx[i],y+dy[i]));
                }
            }
        }
    }
}
void work()
{
    int minn=INF,k,i,tot=0,let[20];
    long long final;
    for (i=2;i<=x;i++) 
        {
            if (minn==dp[(1<<x)-1][i])
            {
                final=min(final,state[(1<<x)-1][i]);
            }
            if (minn>dp[(1<<x)-1][i])
            {
                minn=dp[(1<<x)-1][i];
                final=state[(1<<x)-1][i];
            }
            
        }
    printf("%d\nA",minn);
    while (final) let[++tot]=final%17,final/=17;
    for (i=tot;i;i--) printf("%c",let[i]+'A');
}
int main()
{
    cin>>n>>m>>x;
    for (i=1;i<=n;i++)
        for (j=1;j<=m;j++) 
        {
            cin>>a[i][j];
            if (a[i][j]>='A' && a[i][j]<='Z') 
            {
                zuobiao[a[i][j]-64].x=i;
                zuobiao[a[i][j]-64].y=j;
            }
        }
    for (i=1;i<=x;i++) 
    {
        dijas(i);
        for (j=1;j<=x;j++)
            if (i!=j)
            {
                len[i][j]=dis[zuobiao[j].x][zuobiao[j].y];
            }
    }
//    cout<<len[1][2]<<" "<<len[1][3]<<endl;
//        cout<<len[2][1]<<" "<<len[2][3]<<endl;
//            cout<<len[3][1]<<" "<<len[3][2]<<endl;
//    cout<<zuobiao[3].x<<" "<<zuobiao[3].y<<endl;
    for (i=1;i<(1<<x);i++)
        for (j=1;j<=x;j++) dp[i][j]=INF;
    for (i=1;i<(1<<x);i++)
        for (j=1;j<=x;j++) state[i][j]=INF;
//    for (i=0;i<x;i++) dp[(1<<i)^1][i]=dis[1][i]; 
    state[1][1]=0;
    dp[1][1]=0;
    for (i=1;i<(1<<x);i++)
    {
        for (j=0;j<x;j++)
            if (i&(1<<j))
            {
                for (k=0;k<x;k++)
                    if (k!=j && i&(1<<k))
                    {
                        if (dp[i][k+1]==dp[i^(1<<k)][j+1]+len[j+1][k+1])
                            state[i][k+1]=min(state[i][k+1],state[i^(1<<k)][j+1]*17+k);
                        if (dp[i][k+1]>dp[i^(1<<k)][j+1]+len[j+1][k+1])
                        {
                            dp[i][k+1]=dp[i^(1<<k)][j+1]+len[j+1][k+1];
                            state[i][k+1]=state[i^(1<<k)][j+1]*17+k;
                        }
                    }
            }
    }
    //cout<<state[(1<<x)-1][1]<<endl;
    work();
    return 0;
} 
View Code

 

本想找几题不是dp的来做的,但是这种题目既可以练状压,又可以练dp,还是很有可做性的。

 

新加一题

wannafly camp day3 C 无向图定向(k染色问题)

其实这题在wannafly camp 的时候听到过要用状压做了,但是陆续找了许多篇博客,都没有显而易见的把解法给说出来,但是功夫不负有心人,我终于在今天找到。(原来是搜索方式不对……哭笑)

网上有些人做这题是直接dfs暴力去搜索的,本着想了解一下k染色究竟如何用状压dp来做的好奇,时隔多日,我终于想通了!

 

令dp[s] 为染 s 这个状态图最小的颜色数,其中 s 里 1代表要染色的 0 代表不需要染色! 

那么显然dp[0] = 0  dp[s] = min{dp[s^s0] + 1}  其中s0是s 的子集并且里面的1 在图中不直接相连!  

判断状态s里面的1是否不直接相连可以预处理! 

找s 的子集可以减1进行&运算

最后由于题目的要求,最小染色数-1就是答案了。

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
int n,m,i,x,y,a[20][20],dp[(1<<17)],ok[(1<<17)],j;
bool judge(int x)
{
    for (int i=0;i<n;++i)
    {
        if (x&(1<<i))
            for (int j=0;j<n;++j)
                if (i!=j && (x &(1<<j)))
                {
                    if (a[i+1][j+1]) return false;
                }
    }
    return true;
}
int main()
{
    cin>>n>>m;
    for (i=1;i<=m;i++) 
    {
        cin>>x>>y;
        a[x][y]=1;
        a[y][x]=1;
    }
    for (i=0;i<(1<<n);i++) 
        if (judge(i)) ok[i]=1;    
    dp[0]=0;
    for (i=1;i<(1<<n);++i)
    {
        dp[i]=inf;
        for (j=i;j;j=(j-1) & i)
            if (ok[j]) 
                dp[i]=min(dp[i],dp[i^j]+1);
    }
    cout<<dp[(1<<n)-1]-1;
    return 0;
 } 
View Code

 

posted @ 2020-02-12 17:15  Y-KnightQin  阅读(406)  评论(0编辑  收藏  举报