这周继续学习了dp的专题,主要看了状态dp和概率dp和练了一点数位dp的题。看过状态dp的感受就是,难,不好理解。在一道题需要考虑的状态很多时,我们就可以用到状态dp,通常用二进制来表示状态。当需要表示一个集合有哪些元素时,就可以用2进制的一个整数表示。
有的题,状态比较好表示,像 P1879 [USACO06NOV]Corn Fields G,题意是一个矩阵里有很多格子,每个格子有两种状态,可以放牧和不可以放牧,可以放牧用1表示,否则用0表示,在这块牧场放牛,要求两个相邻的方格不能同时放牛,求放牛的方案。用dp表示的话,我们可以设一个二维数组,一个维度是用来表示第几行的,一个维度就是用来表示哪一行的状态的。接下来到重点,考虑状态怎样表示,用状态压缩,二进制来表示某个状态。比如,0101代表的就是1、3列不放牛,2、4列放牛。再找到状态转移方程:dp [ i ] [ j ] + = dp [ i − 1 ] [ f ],就可以解题了。
#include <bits/stdc++.h>
const int N = 13;
const int M = 1;
const int mod = 100000000;
int st[M],a[M];
int dp[N][M];
using namespace std;
bool judge1(int x)
{ return (x&(x<<1)); }
bool judge2(int i,int x)
{ return (a[i]&st[x]);}
int main()
{ int n,m,x;
while(cin>>n>>m)
{ memset(st,0,sizeof(st));
memset(a,0,sizeof(a));
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++)
{ for(int j=1;j<=m;j++){
cin>>x;
if(x==0) a[i]+=(1<<(j-1));
}
}
int k=0;
for(int i=0;i<(1<<m);i++)
if(!judge1(i)) st[k++]=i;
for(int i=0;i<k;i++)
{ if(!judge2(1,i)) dp[1][i]=1; }
for(int i=2;i<=n;i++)
{ for(int j=0;j<k;j++)
{ if(judge2(i,j)) continue;
for(int f=0;f<k;f++)
{ if(judge2(i-1,f)) continue;
if(!(st[j]&st[f])) dp[i][j]+=dp[i-1][f];
}
}
}
int t=0;
for(int i=0;i<k;i++){
t+=dp[n][i];
t%=mod;
}
cout<<t<<endl;
}
return 0;
}
有些较难的状态dp题,状态表示就不是那么好找了,看了一些题,看的雨里雾里的,没弄明白。还有位运算,之前没接触过,这周刚学的,用起来还有些不熟练。感觉状态dp偏难点,题目都不好理解,可能再多看些题能开窍吧。在状态dp上耗了较多时间没有进展后,我打算先把他放一放,看看概率dp转换下思维。概率dp开了一点头,理解起来倒是不难,正好这学期开了概率统计这门课,知识有重叠的部分。下周再接着看状态dp的题,同时接触下概率dp的题。再接再厉。