[知识点]状态压缩DP

// 此博文为迁移而来,写于2015年7月15日,不代表本人现在的观点与看法。原始地址:http://blog.sina.com.cn/s/blog_6022c4720102w6jf.html

1、前言

动态规划,永远的痛。

好了不扯远了。状态压缩动态规划,其实看名字还是较好理解的。我们在动态规划的时候,最重要的就在于状态的设计和状态转移方程。那么,如果当我们状态过多导致时间或空间不够的饿时候,就可以用到状态压缩。王队(@wyh2000)说状态压缩DP难起来的话会很难,但是今天我们只讲最最最基础的状态压缩方式,以后再慢慢补充。

2、区别

       ①有一个1*n的棋盘(n<=80),需要在上面放置k个棋子,使棋子之间不相邻,求方案数。

那么这是一道很简单的棋盘型DP。设f[i][j][0]为前i个放置j个棋子的方案数且第i位必放,f[i][j][1]为前i个放置j个棋子的方案数且第i位必不放。则存在方程:

       ②有一个m*n的棋盘(n*m<=80),需要在上面放置k个棋子,使棋子之间不相邻,求方案数。

多了一个行的状态,这就让人费解了——我们并不能设置一个四维方程来表示状态。原来我们每一行只有一个格子,现在多个格子怎么表示呢?这里就要用到状态压缩了。这里提到的状态压缩的方式只是其中一种,相对比较简单的一种——二进制转换。我们注意到题目有一个特别鬼畜的数据范围——n*m<=80。这意味着什么?9*9=81>80,则min(n,m)的最大值为8。我们将m,n中较小的一个看做行(易得行列转换依旧等价),一行至多8个格子,我们令当前状态下格子若放置了棋子则记为1,未放置记为0,那么我们可以将一行的状态表示为一个二进制数,继而在状态转移的时候再转换为十进制。前文提到了最多8个数,所以数组中数值最大为2^8=256。设f[i][j][k]表示当前第i列,使用了j个棋子,当前行的状态为k(一个由二进制数转换过来的十进制数),则状态转移方程为:


            其中k`表示上一行的状态,num(k)表示在当前行状态为k的情况下棋子的总个数。

当然我们要注意——如何判断当前这一行与上一行是否存在相邻的棋子?利用按位异或即可。见代码。

----------------------------------------------------------------------------------------------------

#include cstdio
#include cstring
#include algorithm
using namespace std;

long long f[81][1<<9][21];

inline int getNum(int x)
{
        int s=0,tmp=0;
        while (x)
        {
                if (s && (x & 1)) return -1;
                if (s=(x & 1)) tmp++;
                x=x>>1;
        }
        return tmp;
}

int main()
{
        int n,m,t;
        while (scanf("%d %d %d",&n,&m,&t)!=EOF)
        {
                if (n<m)  swap(n,m);
                memset(f,0,sizeof(f));
                f[0][0][0]=1;
                for (int i=1;i<=n;i++)
                        for (int r=0;r<=t;r++)
                                for (int j=0;j<(1<<m);j++) // 当前的状态 
                                {
                                        int num=getNum(j); // 棋子个数 
                                        if (num==-1 || num>r) continue;
                                        for (int k=0;k<(1<<m);k++) // 上次的状态转移 
                                        {
                                                if (getNum(k)==-1 || k&j) continue;
                                                f[i][j][r]+=f[i-1][k][r-num];
                                        }
                                }
                long long ans=0;
                for (int i=0;i<(1<<m);i++) ans+=f[n][i][t];
                printf("%lld\n",ans);
        }
        return 0;
}

-----------------------------------------------------------------------------------------------------

3、例题

分析:同样地,这道题用f[i][j][k]表示从(1,1)到(i,j)子矩阵中当所走路径状态为k的时候所得的最小权值,那么状态则是根据所走路径转换为二进制在转换为十进制来转移,就不多说了,看代码。

代码:

---------------------------------------------------------------------------------------------------

#include<cstdio>
#include<cstring>
#define MAXN 11
#define INF 0x3f3f3f3f

int min(int a,int b) { return (a<b)?a:b };
 
const int two[MAXN]={1,2,4,8,16,32,64,128,256,512};

int map[MAXN][MAXN],f[MAXN][MAXN][(1<<10)+1],ans,ret,tot;

int getNum(int ki,int now)
{
        int temp=ki;
        for (int i=9;i>=0;i--) if (temp>=two[i]) { temp-=two[i]; if (i==now) return ki; }
        return (ki+two[now]);
}

int main()
{
        memset(f,INF,sizeof(f));
        for (int i=1;i<=10;i++)
                for (int j=1;j<=10;j++) scanf("%d",&map[i][j]);
        int t1=two[map[1][1]],t2=0;
        f[1][1][t1]=map[1][1];
        for (int i=2;i<=10;i++) 
        {
                t2=getNum(t1,map[1][i]);
                f[1][i][t2]=f[1][i-1][t1]+map[1][i];
                t1=t2;
        }
        t1=two[map[1][1]];
        for (int i=2;i<=10;i++) 
        {
                t2=getNum(t1,map[i][1]);
                f[i][1][t2]=f[i-1][1][t1]+map[i][1];
                t1=t2;
        }
        for (int i=2;i<=10;i++)
                for (int j=2;j<=10;j++)
                {
                        for (int ki=1;ki<=(1<<10)-1;ki++)
                        {
                                if (f[i-1][j][ki]==INF) continue;
                                int k=getNum(ki,map[i][j]);
                                f[i][j][k]=min(f[i][j][k],f[i-1][j][ki]+map[i][j]);
                        }
                        for (int ki=1;ki<=(1<<10)-1;ki++)
                        {
                                if (f[i][j-1][ki]==INF) continue;
                                int k=getNum(ki,map[i][j]);
                                f[i][j][k]=min(f[i][j][k],f[i][j-1][ki]+map[i][j]);
                        }
         }
        for (int i=1;i<=1023;i++) if (f[3][3][i]!=INF) printf("i=%d %d\n",i,f[3][3][i]);
        printf("%d",f[10][10][1023]);
        return 0;
}

---------------------------------------------------------------------------------------------------

时间: 2024-10-08 14:13:06

[知识点]状态压缩DP的相关文章

POJ 3254 Corn Fields 状态压缩DP (C++/Java)

http://poj.org/problem?id=3254 题目大意: 一个农民有n行m列的地方,每个格子用1代表可以种草地,而0不可以.放牛只能在有草地的,但是相邻的草地不能同时放牛, 问总共有多少种方法. 思路: 状态压缩的DP. 可以用二进制数字来表示放牧情况并判断该状态是否满足条件. 这题的限制条件有两个: 1.草地限制. 2.相邻限制. 对于草地限制,因为输入的时候1是可以种草地的. 以"11110"草地分析,就只有最后一个是不可以种草的.取反后得00001  .(为啥取反

HDU1565(状态压缩dp)

方格取数(1) Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 8170    Accepted Submission(s): 3095 Problem Description 给你一个n*n的格子的棋盘,每个格子里面有一个非负数.从中取出若干个数,使得任意的两个数所在的格子没有公共边,就是说所取的数所在的2个格子不能相邻,并且取出的数

HDU 3001【状态压缩DP】

题意: 给n个点m条无向边. 要求每个点最多走两次,要访问所有的点给出要求路线中边的权值总和最小. 思路: 三进制状态压缩DP,0代表走了0次,1,2类推. 第一次弄三进制状态压缩DP,感觉重点是对数据的预处理,利用数组分解各个位数,从而达到类似二进制的目的. 然后就是状态的表示,dp[s][i]表示状态s时到达i的最优值. 状态转移也一目了然,不废话. #include<stdio.h> #include<string.h> #include<algorithm> u

Victor and World(spfa+状态压缩dp)

题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=5418 Victor and World Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 262144/131072 K (Java/Others)Total Submission(s): 958    Accepted Submission(s): 431 Problem Description After trying hard fo

poj 3311 Hie with the Pie(状态压缩dp)

Description The Pizazz Pizzeria prides itself in delivering pizzas to its customers as fast as possible. Unfortunately, due to cutbacks, they can afford to hire only one driver to do the deliveries. He will wait for 1 or more (up to 10) orders to be

HDU--1074(状态压缩DP)

典型的状态压缩DP,给出了每件作业的截止时间和花费,求让老师扣分最少的写作业方式.把完成n种作业用状态2^n-1表示,dp[s]表示 完成状态s时,最小扣分.比如“111”,那么可以由“011”,“110”,“101”转移过来,分别表示选了0,1号作业,1,2号作业,0,2号作业. t[s]表示状态S记录的总时间.dp[s] = min{dp[j]+c[k] - d[k]},其中j = i^(1<<k),0<k<n;pre[s]表示状态s完成时,最末尾完成的作业, #include

poj 3254 Corn Fields(状态压缩dp)

Description Farmer John has purchased a lush new rectangular pasture composed of M by N (1 ≤ M ≤ 12; 1 ≤ N ≤ 12) square parcels. He wants to grow some yummy corn for the cows on a number of squares. Regrettably, some of the squares are infertile and

poj 2411 Mondriaan&#39;s Dream(状态压缩+dp)

 题意:用1*2砖块铺满n*m的房间. 思路转自:http://www.cnblogs.com/scau20110726/archive/2013/03/14/2960448.html 因为这道题输入范围在11*11之间,所以可以先打表直接输出.......... 状态压缩DP 经典覆盖问题,输入n和m表示一个n*m的矩形,用1*2的方块进行覆盖,不能重叠,不能越出矩形边界,问完全覆盖完整个矩形有多少种不同的方案 其中n和m均为奇数的话,矩形面积就是奇数,可知是不可能完全覆盖的.接着我们来看

1252 - Twenty Questions(状态压缩DP)

经典的状态压缩DP .  有没有感觉这道题和什么东西有点像?  没错,是01背包 . 将特征看作物品 , 只不过这里的状态有点复杂, 需要用一个集合才能表示它, 所以我们用d[s][a]来表示,已经询问了特征集s , 假设我们要猜的物品是w ,w所具备的特征集为a ,此时还要询问的最小次数 .   显然a是s的子集,而且要注意本题的要求, 求的是最小化的最大询问次数 .也就是说无论猜哪个物品,猜这么多次一定能猜到 . 那么状态如何转移呢? 就像背包问题,对于一个特征k ,我们要抉择:要k还是不要