状压dp
Posted stungyep
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了状压dp相关的知识,希望对你有一定的参考价值。
状态压缩DP
1.状态压缩的定义
状态压缩的定义:我们知道任何一个二进制都可以对应唯一的十进制数,反过来也成立。所以我们可以用一个数来代替一组数从而降低维数。这种解题手段我们叫做状态压缩。
举个例子:如果数组中的某一行全是0或全是1,例如000,001,我们可以将000用0表示,001用1来表示,这样一来就降低了维数,起到了状态压缩的作用。状态压缩dp就是基于此的dp。
2.位运算
状压dp与位运算关系密切,所以先了解一下位运算。
基本的一些位运算(如&,|,^,~,<<,>>)的基本用法就不多做介绍。来说一下它们的一些运用:
1:判断一个数x的二进制下第 i 位是不是1:
if((1<<(i-1))&x>0)
;2:将一个数x的二进制下第 i 位改为1:
x=x|(1<<(i-1))
;3:把一个数二进制下最靠右的第一个1去掉:
x=x&(x-1)
......待补充
3.入门例题
1:poj-3254
题意:一个n*m的草地,1代表有草,0有没。两个草不能相邻,问有多少种选法。
状态:dp[i][state[j]]
表示前 i 行,第 i 行采用第 j 个状态时得到的总方案数。所以可得状态转移方程:[dp[i][state(j)]=dp[i-1][state(k1)]+dp[i-1][state(k2)]+......+dp[i-1][state(kn)]。]
show code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e8;
const int maxn=1e5+10;
int state[maxn],dp[15][maxn];
int arr[15][15],n,m,tot;
void init() //求出合理状态打个表
{
int maxlen=1<<12;
for(int i=0;i<maxlen;++i){
if((i&(i<<1))==0) state[++tot]=i; //与运算要打括号!!!
}
}
bool check(int row,int x)
{
for(int i=1;i<=m;++i)
{
if(x&(1<<(i-1))&&!arr[row][i]) //检查一下x的所有位是否与已求出的合法状态冲突
return false;
}
return true;
}
int main()
{
init();
while(scanf("%d%d",&n,&m)!=EOF)
{
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
scanf("%d",&arr[i][j]);
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;++i)
{
for(int j=1;state[j]<(1<<m);++j)
{
if(!check(i,state[j])) continue; //保证下面的情况都与输入不冲突
if(i==1){
dp[i][j]=1;
continue;
}
for(int k=1;state[k]<(1<<m);++k){ //根据状态转移方程而来
if((state[k]&state[j])==0) //同一列没有两个1,两个数做与运算一定要打括号!!!!
dp[i][j]+=dp[i-1][k];
}
}
}
/*for(int i=1;i<=n;++i){
for(int j=1;state[j]<(1<<m);++j)
printf("%d ",dp[i][j]);
printf("
");
}*/
ll res=0;
for(int i=1;state[i]<(1<<m);++i)
res=(res+dp[n][i])%mod;
printf("%d
",res);
}
return 0;
}
2:poj-3311
题意:一个有向图,一个点可以走多次,问从0号点开始走把所有点都走一遍且最后回到原点的最短路径是多少。
#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
const int maxn=15;
int arr[maxn][maxn];
int n,dp[1<<11][maxn];
int main()
{
while(scanf("%d",&n)&&n)
{
for(int i=0;i<=n;++i)
for(int j=0;j<=n;++j)
scanf("%d",&arr[i][j]);
for(int k=0;k<=n;++k) //预处理求出任意两点之间的最小距离
for(int i=0;i<=n;++i)
for(int j=0;j<=n;++j)
if(arr[i][k]+arr[k][j]<arr[i][j])
arr[i][j]=arr[i][k]+arr[k][j];
for(int s=0;s<(1<<n);++s) //s表示状态
{
for(int i=1;i<=n;++i) //状态要从1开始表示,但其实是第i+1号节点(从1开始编号的话)
{
if(s&(1<<(i-1)))
{
if(s==(1<<(i-1))) dp[s][i]=arr[0][i]; //如果刚好只到城市i,也是边界情况
else{
dp[s][i]=inf;
for(int j=1;j<=n;++j) //找一个合适的点使转过去之后距离最小
if ((s>>(j-1))&1 && j!=i )
dp[s][i]=min(dp[s][i],dp[s^(1<<(i-1))][j]+arr[j][i]);
//在s状态没有经过城市i中找一点,使得距离更短
}
}
}
}
int res=dp[(1<<n)-1][1]+arr[1][0];
for(int i=2;i<=n;++i)
res=min(res,dp[(1<<n)-1][i]+arr[i][0]);
printf("%d
",res);
} s
}
3:HDU-1074
题意:有多门作业,每门作业有截止日期和花费天数,没门作业超过截止期限一天就要减去1分,问最少减几分,并输出做作业顺序方案。
因为作业最多只有15个,所以可以用状压dp来记录,方案在dp中加个pre和now即可,用栈记录输出一下,代码:
#include<bits/stdc++.h>
using namespace std;
const int inf=1<<30;
const int maxn=1e6;
struct node
{
string cur;
int dead;
int cost;
}arr[20];
int n,T;
struct Node
{
int time;
int score;
int pre;
int now;
}dp[maxn];
int main()
{
ios::sync_with_stdio(false);
cin>>T;
while(T--)
{
memset(dp,0,sizeof(dp));
cin>>n;
for(int i=1;i<=n;++i)
cin>>arr[i].cur>>arr[i].dead>>arr[i].cost;
for(int s=1;s<(1<<n);++s)
{
dp[s].score=inf;
for(int i=n;i>=1;--i) //按字典序顺序输入,所以逆向枚举即可
{
int temp=1<<(i-1); //看i是否完成
if(s&temp)
{
int past=s-temp; //past为除了i的剩余课程
int st=dp[past].time+arr[i].cost-arr[i].dead; //st为扣得分数
if(st<0) st=0;
if(dp[past].score+st<dp[s].score)
{
dp[s].score=st+dp[past].score;
dp[s].now=i; //由i转移过来
dp[s].pre=past;
dp[s].time=dp[past].time+arr[i].cost;
}
}
}
//cout<<"state is "<<s<<dp[s].score<<endl;
}
stack<int> s;
int t=(1<<n)-1;
cout<<dp[t].score<<endl;
while(t)
{
s.push(dp[t].now);
t=dp[t].pre;
}
while(!s.empty())
{
cout<<arr[s.top()].cur<<endl;
s.pop();
}
}
}
以上是关于状压dp的主要内容,如果未能解决你的问题,请参考以下文章