由于队友XJP在做状态DP,我也跟着做了一发,作为数学系的学生,记得两年前在运筹学中学到了一点点动态规划的皮毛,当初对动态规划不屑一顾,后来接触了背包算法,以及各种编程中用到了不少动态规划的知识才发现动态规划之博大精深,不是动态规划不强大,而是自己太弱了,学习动态规划是学习了这里的文章。
做的第一道状态DP是 zoj 3777Problem Arrangement 题意是随机安排题目使得兴趣值大于给定M<=300的期望,题目数量1<=N<=12
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=13;
const int M=502;
int dp[1<<N][M];
int a[N][N],f[N];
void init(){
f[0]=1;
for(int i=1;i!=N;++i){
f[i]=f[i-1]*i;
}
}
int gcd(int a,int b){
return b?gcd(b,a%b):a;
}
int main(){
int T,n,m;
init();
// freopen("in.txt","r",stdin);
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
for(int i=0;i!=n;++i){
for(int j=0;j!=n;++j){
scanf("%d",&a[i][j]);
}
}
memset(dp,0,sizeof(dp));
dp[0][0]=1;
for(int i=0;i!=(1<<n);++i){
int tmp=0;
for(int j=0;j!=n;++j){
if(i&(1<<j)) ++tmp;
}
for(int j=0;j!=n;++j){
if(i&(1<<j)) continue;
for(int k=0;k<=m;++k){
if(k+a[tmp][j]>=m) dp[i|(1<<j)][m]+=dp[i][k];
else dp[i|(1<<j)][k+a[tmp][j]]+=dp[i][k];
}
}
}
if(dp[(1<<n)-1][m]){
int tmp=gcd(dp[(1<<n)-1][m],f[n]);
printf("%d/%d\n",f[n]/tmp,dp[(1<<n)-1][m]/tmp);
}else printf("No solution\n");
}
return 0;
}
第二题
poj3254Corn Fields 题意在可以种草的地方 种草且草不相邻的种数mod1e9
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=13;
const int MOD=100000000;
int a[N],dp[1<<N][N],next[1<<N];
int main(){
int n,m,tmp;
// freopen("in.txt","r",stdin);
while(scanf("%d%d",&n,&m)!=EOF){
int mm=1<<m;
memset(dp,0,sizeof(dp));
memset(a,0,sizeof(a));
tmp=0;
for(int i=0;i!=mm;++i){
if((i&(i>>1))==0){
next[tmp]=i;tmp=i;
}
}
next[tmp]=mm;
for(int i=0;i!=n;++i){
for(int j=0;j!=m;++j){
scanf("%d",&tmp);
if(tmp==0) a[i]|=(1<<j);
}
}
for(int i=0;i!=mm;i=next[i]){
if((i&a[0])==0) dp[i][0]=1;
}
for(int j=1;j!=n;++j){
for(int i=0;i!=mm;i=next[i]){
for(int k=0;k!=mm;k=next[k]){
if((k&a[j])==0 && (i&k)==0) dp[k][j]=(dp[k][j]+dp[i][j-1])%MOD;
}
}
}
int ans=0;
for(int i=0;i!=mm;i=next[i]){
ans=(ans+dp[i][n-1])%MOD;
}
printf("%d\n",ans);
}
return 0;
}
第三题 poj1185 炮兵阵地 题意就是在平原放能放置的最大大炮数且满足同行同列的大炮之间距离大于2
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
typedef __int64 LL;
using namespace std;
const int N=11;
int a[102],next[1<<N],p[70];
int dp[102][70][70],num[1<<N];
char s[102];
int bcount(int x){
int cnt=0;
while(x){
++cnt;
x&=(x-1);
}
return cnt;
}
inline int max(int a,int b){
return a>b?a:b;
}
int main(){
int n,m,tmp;
//freopen("in.txt","r",stdin);
while(scanf("%d%d",&n,&m)!=EOF){
int mm=1<<m;
memset(dp,0,sizeof(dp));
memset(a,0,sizeof(a));
tmp=0;
for(int i=0;i!=mm;++i){
if((i&(i>>1))==0 && (i&(i>>2))==0){
next[tmp]=i;tmp=i;
}
}
next[tmp]=mm;
int len=0;
for(int i=0;i!=mm;i=next[i]) p[len++]=i;
for(int i=0;i!=len;++i) num[p[i]]=bcount(p[i]);
for(int i=0;i!=n;++i){
scanf("%s",s);
for(int j=0;j!=m;++j){
if(s[j]=='H') a[i]|=(1<<j);
}
}
for(int i=0;i!=len;++i){
if((p[i]&a[0])==0) dp[0][0][i]=num[p[i]];
}
if(n>1) for(int i=0;i!=len;++i){
for(int j=0;j!=len;++j){
if((p[j]&a[1])==0 && (p[i]&p[j])==0) dp[1][i][j]=dp[0][0][i]+num[p[j]];
}
}
for(int i=2;i<n;++i){
for(int j=0;j!=len;++j){
for(int k=0;k!=len;++k){
if(p[j]&p[k]) continue;
for(int l=0;l!=len;++l){
if(p[j]&p[l]) continue;
if(p[k]&p[l]) continue;
if(a[i]&p[l]) continue;
dp[i][k][l]=max(dp[i][k][l],dp[i-1][j][k]+num[p[l]]);
}
}
}
}
int ans=0;
for(int i=0;i!=len;++i){
for(int j=0;j!=len;++j){
ans=max(ans,dp[n-1][i][j]);
}
}
printf("%d\n",ans);
}
return 0;
}
第四题 TSP旅行商问题,不过有点小变化
poj3311Hide with the pie
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
typedef __int64 LL;
using namespace std;
const int N=12;
const int INF=1e8;
int a[N][N],dp[1<<N][N];
inline int min(int x,int y){
return x<y?x:y;
}
int main(){
int n;
while(scanf("%d",&n)!=EOF&&n){
++n;
int nn=1<<n;
for(int i=0;i!=n;++i){
for(int j=0;j!=n;++j){
scanf("%d",&a[i][j]);
}
}
for(int k=0;k!=n;++k){ //Floyd
for(int i=0;i!=n;++i){
for(int j=0;j!=n;++j){
a[i][j]=min(a[i][j],a[i][k]+a[k][j]);
}
}
}
for(int i=0;i!=nn;++i){
for(int j=0;j!=n;++j){
dp[i][j]=INF;
}
}
dp[1][0]=0;
for(int i=0;i!=nn;++i){
for(int j=0;j!=n;++j){
if(i&(1<<j)) continue;
for(int k=0;k!=n;++k){
if(i&(1<<k)) dp[i|(1<<j)][j]=min(dp[i|(1<<j)][j],dp[i][k]+a[k][j]);
}
}
}
int ans=INF;
for(int i=0;i!=n;++i){
ans=min(ans,dp[nn-1][i]+a[i][0]);
}
printf("%d\n",ans);
}
return 0;
}
第四题
hdu3681Prison Break本质上与上题没啥区别,只是需要转化一下把距离求出来,并且这里需要二分查找答案
#include <iostream>
#include <cstdio>
#include <vector>
#include <queue>
#include <cstring>
#include <algorithm>
typedef __int64 LL;
using namespace std;
const int N=16;
struct Node{
int x,y;
Node(int _x=0,int _y=0):x(_x),y(_y){}
}a[N];
char str[N];
int map[N][N];
int dt[N][N][N][N],n,m,len,ans;
int dp[1<<N][N],dist[N][N];
int dir[4][2]={{-1,0},{1,0},{0,1},{0,-1}};
void bfs(int s){
queue<Node> q;
while(!q.empty()) q.pop();
int sx=a[s].x,sy=a[s].y;
dt[sx][sy][sx][sy]=0;
q.push(a[s]);
while(!q.empty()){
Node cur=q.front();
q.pop();
int x=cur.x,y=cur.y;
for(int i=0;i!=4;++i){
int tx=x+dir[i][0];
int ty=y+dir[i][1];
if(tx<0||tx>=m||ty<0||ty>=m||map[tx][ty]<0) continue;
if(dt[sx][sy][tx][ty]==-1){
dt[sx][sy][tx][ty]=dt[sx][sy][x][y]+1;
q.push(Node(tx,ty));
}
}
}
}
bool check(int num){
int top=1<<len;
memset(dp,-1,sizeof(dp));
dp[1][0]=num;
for(int i=0;i!=top;++i){
for(int j=0;j!=len;++j){
if((i&(1<<j))==0||dp[i][j]==-1) continue;
if((i&ans)==ans) return true;
for(int k=0;k!=len;++k){
if((i&(1<<k))||dist[j][k]==-1||i==j) continue;
dp[i|(1<<k)][k]=max(dp[i|(1<<k)][k],dp[i][j]-dist[j][k]);
if(dp[i|(1<<k)][k]>=0&&map[a[k].x][a[k].y]==2) dp[i|(1<<k)][k]=num;
}
}
}
return false;
}
int main(){
// freopen("in.txt","r",stdin);
while(scanf("%d%d",&n,&m)!=EOF&&(n+m)){
len=1;ans=0;
for(int i=0;i!=n;++i){
scanf("%s",str);
for(int j=0;j!=m;++j){
if(str[j]=='S') map[i][j]=0;
else if(str[j]=='D') map[i][j]=-1;
else if(str[j]=='F'){
map[i][j]=0;
a[0]=Node(i,j);
}else{
a[len++]=Node(i,j);
if(str[j]=='Y'){
map[i][j]=1;
ans|=1<<(len-1);
}
else map[i][j]=2;
}
}
}
memset(dt,-1,sizeof(dt));
for(int i=0;i!=len;++i) bfs(i);
for(int i=0;i!=len;++i){
for(int j=0;j!=i;++j){
dist[j][i]=dist[i][j]=dt[a[i].x][a[i].y][a[j].x][a[j].y];
}
dist[i][i]=0;
}
int low=0,high=303;
while(low<=high){
int mid=(low+high)>>1;
if(check(mid)) high=mid-1;
else low=mid+1;
}
if(low==304) printf("-1\n");
else printf("%d\n",low);
}
return 0;
}
总结可以认为第一个是无限状态DP,第二个是1状态DP,第三个是2状态,类似于背包前两个比较好处理,而最后一个处理起来就相对较麻烦,另外状态dp适合于求解n!以及2^n的复杂度问题,它并没有降低复杂度,只是减少了枚举,并能很方便的解决小规模问题