POJ 1185 炮兵阵地
一般这类题目列会很小10左右,我们需要枚举所有行,对于当前行,其放炮的位置只与上一行和上上行有关,我们记当前行状态和上一行状态便可转移了,dp数组开3维即可。
在枚举每一行放炮兵的方法时,可以预处理方便获得所有可能放法。具体细节见代码。总体复杂度O(Row*70*70*70),70是预处理得到的每一行最多放法。打表很容易看出来。
#include<stdio.h> #include<stdlib.h> #include<string.h> char map[105][12]; int n,m,hmap[105],shell[70],permutation[70],all,dp[2][70][70]; void init(){ int i,k; all=0; memset(shell,0,sizeof(shell)); for(i=0;i<1<<m;i++){ k=i; if( (i&(k>>1)) || (i&(k>>2)) ) continue; shell[all]+=k%2; k/=2; while(k!=0){ shell[all]+=k%2; k/=2; } permutation[all++]=i; } } void dps(){ int row,i,j,k,h,max=0; memset(dp,0,sizeof(dp)); row=0; for(i=0;i<n;i++){ for(j=0;j<all;j++){ if(permutation[j]&hmap[i]) continue; if(i==0){ dp[row][j][0]=shell[j]; } else if(i==1){ for(k=0;k<all;k++){ if(permutation[k]&hmap[i-1]){ continue; } if(permutation[k]&permutation[j]){ continue; } if(dp[row][j][k]<dp[(row+1)%2][k][0]+shell[j]) dp[row][j][k]=dp[(row+1)%2][k][0]+shell[j]; } } else{ for(k=0;k<all;k++){ if(permutation[k]&hmap[i-1]){ continue; } if(permutation[k]&permutation[j]){ continue; } for(h=0;h<all;h++){ if(permutation[h]&hmap[i-2]){ continue; } if(permutation[h]&permutation[j]){ continue; } if(permutation[h]&permutation[j]){ continue; } if(dp[row][j][k]<dp[(row+1)%2][k][h]+shell[j]) dp[row][j][k]=dp[(row+1)%2][k][h]+shell[j]; } } } } row=(row+1)%2; } for(i=0;i<all;i++){ for(j=0;j<all;j++){ if(max<dp[(row+1)%2][i][j]) max=dp[(row+1)%2][i][j]; } } printf("%d\n",max); } int main() { int i,j,k; scanf("%d %d",&n,&m); memset(hmap,0,sizeof(hmap)); for(i=0;i<n;i++) scanf("%s",map[i]); for(i=0;i<n;i++){ for(j=0;j<m;j++){ if(map[i][j]==‘H‘) hmap[i]+=1<<j; } } init(); dps(); }
zoj 3755Mines
前几天的浙大月赛真题。
逐行扫描,取最小值的简单状压DP
#include <cstdio> #include <algorithm> #include <cstring> using namespace std; int mp[11][11]; int dp[2][1024]; int num[1024]; int main(){ int n,m; while(scanf("%d %d",&n,&m)!=EOF){ for(int i=0;i<n;i++) for(int j=0;j<m;j++) scanf("%d",&mp[i][j]); int now=0; int sta=1<<n; for(int i=0;i<sta;i++){ num[i]=0; for(int j=0;j<n;j++) num[i]+=(i&(1<<j))?1:0; dp[now][i]=num[i]; } now=!now; for(int t=0;t<m;t++){ memset(dp[now],-1,sizeof(dp[now])); for(int i=0;i<sta;i++) for(int j=0;j<sta;j++){ if(dp[!now][j]==-1)continue; int tmp[11]; for(int k=0;k<n;k++)tmp[k]=((i&(1<<k))?1:0)+((j&(1<<k))?1:0); bool flag=1; if((tmp[0]+tmp[1])!=mp[0][t])flag=0; int k; for(k=1;k<n-1 && flag;k++){ if((tmp[k-1]+tmp[k]+tmp[k+1])!=mp[k][t])flag=0; } if(k==n-1 && (tmp[k-1]+tmp[k])!=mp[k][t])flag=0; if(flag){ if(dp[now][i]==-1)dp[now][i]=dp[!now][j]+num[i]; else dp[now][i]=min(dp[now][i],dp[!now][j]+num[i]); } } now=!now; } int ans=-1; for(int i=0;i<1024;i++)if(dp[!now][i]!=-1){ if(ans==-1) ans=dp[!now][i]; else ans=min(ans,dp[!now][i]); } printf("%d\n",ans); } }
这类题目有两种做法,一是直接按行状压,另外一种是轮廓线法(因为矩形是1*2的,所以轮廓线只需要维护长为列长的状态即可2^10),
状压做法如下(代码转自http://blog.csdn.net/xiaozhuaixifu/article/details/10221341)
先初始化第一行的方法,2^10判断每个状态是否合法并记录。然后处理其他行时,要满足当前行的前面所有行都匹配好即可,所以状态只需要记录二维,转移时判断当前行的状态nStatusA能否补上上一行状态nStatusB漏掉的格子并且剩余的nStatusA是1*2的矩形拼成的即可。总体复杂度O(Row*(2^Col)*(2^Col)*Col)
#include <cstdio> #include <cmath> #include <algorithm> #include <cstring> #include <iostream> using namespace std; #define MAX_ROW 11 #define MAX_STATUS 2048 long long DP[MAX_ROW][MAX_STATUS]; int g_Width, g_Height; bool TestFirstLine(int nStatus) //test the first line { int i = 0; while( i < g_Width) { if(nStatus & (0x1 << i)) { if( i == g_Width -1 || (nStatus & (0x1 << (i+1))) == 0) { return false; } i += 2; } else { i++; } } return true; } bool CompatablityTest(int nStatusA, int nStatusB) // test if status (i, nStatusA) and (i-1, nStatusB) is compatable. { int i = 0; while( i < g_Width) { if( (nStatusA & (0x1 << i)) == 0) { if((nStatusB & (0x1 << i)) == 0) { return false; } i++; } else { if((nStatusB & (0x1 << i)) == 0 ) { i++; } else if( (i == g_Width - 1) || ! ( (nStatusA & (0x1 << (i+1))) && (nStatusB & (0x1 << (i + 1)))) ) { return false; } else { i += 2; } } } return true; } int main() { int i,j; int k; while(scanf("%d%d", &g_Height, &g_Width) != EOF ) { if(g_Width == 0 && g_Height == 0) { break; } if(g_Width > g_Height) { swap(g_Width, g_Height); } int nAllStatus = 2 << (g_Width-1); memset(DP, 0, sizeof(DP)); for( j = 0; j < nAllStatus; j++) { if(TestFirstLine(j)) { DP[0][j] = 1; } } for( i = 1; i < g_Height; i++) { for( j = 0; j < nAllStatus; j++)// iterate all status for line i { for( k = 0; k < nAllStatus; k++) // iterate all status for line i-1 { if(CompatablityTest(j, k)) { DP[i][j] += DP[i-1][k]; } } } } printf("%lld\n", DP[g_Height-1][nAllStatus - 1]); } return 0; }
轮廓线做法(转自http://blog.csdn.net/cc_again/article/details/9390475)
简单轮廓线dp,时刻记录一个长为Col的轮廓线即可,转移也比较简单,当前不放,朝上的矩形,朝左的矩形三种情况讨论一下即可。总体复杂度O(Row*Col*(2^Col))。可以看出来轮廓线做法复杂度更优!
#include<iostream> #include<cmath> #include<cstdio> #include<cstdlib> #include<string> #include<cstring> #include<algorithm> #include<vector> #include<map> #include<set> #include<stack> #include<list> #include<queue> #define eps 1e-6 #define INF 0x1f1f1f1f #define PI acos(-1.0) #define ll long long #define lson l,m,(rt<<1) #define rson m+1,r,(rt<<1)|1 using namespace std; /* freopen("data.in","r",stdin); freopen("data.out","w",stdout); */ ll dp[2][1<<15]; //dp[cur][s]表示当前格子的轮廓线状态为s的情况下的总数 int n,m,cur; void update(int a,int b) { if(b&(1<<m)) //将前一个格子的最高位,如果是1,则置零,并更新 dp[cur][b^(1<<m)]+=dp[1-cur][a]; } int main() { while(scanf("%d%d",&n,&m)&&m+n) { //if(m+n) if(m>n) swap(n,m); memset(dp,0,sizeof(dp)); dp[0][(1<<m)-1]=1; //第一行只能横着放,把这个状态初始化为1 cur=0; int lim=1<<m; for(int i=0;i<n;i++) for(int j=0;j<m;j++) //对每个格子,从前面一个格子推过来 { cur^=1; memset(dp[cur],0,sizeof(dp[cur])); for(int k=0;k<lim;k++) //枚举前一个格子的所有状态 { update(k,k<<1); //不放 if(i&&!(k&(1<<(m-1))))//竖着放 update(k,(k<<1)^(1<<m)^1); //将放着的两点置1 if(j&&!(k&1)) update(k,(k<<1)^3); //横着放 } } printf("%lld\n",dp[cur][lim-1]); } return 0; }
hdu 4804 Campus Design
同样这题和上题类似,也有两种做法。
直接状压法。(转自http://www.cnblogs.com/wulangzhou/p/3451756.html)
这个思想也和上一个题目类似,在本行内用1*1和1*2横放填充(这个用dfs实现),在行间转移原则是本行要先用1*2的竖放把上一行的状态填满,然后再在剩下的格子里用1*1和1*2横放填充即可。总体复杂度O(Row*(2^Col)*(2^Col)*D),当然由于行间转移时转移到的状态数是小于(2^Col)的,所以8s的时限凑合可以。
#include<iostream> #include<stdio.h> #include<cstring> #include<algorithm> #include<cmath> #define mod 1000000007 using namespace std; int N,M,C,D; __int64 dp[4][3000][22]; char map[105][22]; void dfs( int sta,int lev,int stu,int n,int k,__int64 num ) { if( sta >= M || k > D)return; dfs( sta+1,lev,stu,n,k,num ); if( map[lev][sta] == ‘1‘ && (stu&(1<<sta)) == 0 ) { int t = stu+(1<<sta); dp[n][t][k+1]+=num; dp[n][t][k+1] %= mod; dfs(sta+1,lev,t,n,k+1,num); } if( sta + 1 < M && map[lev][sta] == ‘1‘ && map[lev][sta+1] == ‘1‘ ) if( (stu&(1<<sta)) == 0 && (stu&(1<<(sta+1))) == 0 ) { int t = stu+(1<<sta); t = t+(1<<(sta+1)); dp[n][t][k]+=num; dp[n][t][k] %= mod; dfs(sta+2,lev,t,n,k,num); } } void work( ) { int i,j,k,s,t=1,num = (1<<M); memset( dp,0,sizeof(dp) ); dp[t][num-1][0] = 1; for( i = 1; i <= N; i++ ) { s = t^1; for( j = 0; j < num; j++ ) for( k = 0; k <= D; k++ ) { if( dp[t][j][k] == 0 )continue; __int64 stu = 0; bool fell = false; for( int x = 0; x < M; x++ ) if( map[i-1][x] != ‘0‘ && (j&(1<<x)) == 0 ) { if( map[i][x] == ‘0‘ ) fell = true; else stu += (1<<x); } if( fell )continue; dp[s][stu][k] = dp[t][j][k]; dfs( 0,i,stu,s,k,dp[t][j][k] ); } for( j = 0; j < num; j++ ) for( k = 0; k <= D; k++ ) dp[t][j][k] = 0; t = s; } __int64 res = 0; int now = 0; for( j = 0; j < M; j++ )if( map[N][j] != ‘0‘ ) now +=(1<<j); for( j = C; j <= D; j++ ) res += dp[t][now][j],res %= mod; printf("%I64d\n",res); } int main( ) { while( scanf("%d%d%d%d",&N,&M,&C,&D) != EOF ){ memset(map,‘0‘,sizeof(map)); for( int i = 1; i <= N; i++ ) scanf("%s",map[i]); work( ); } return 0; }
轮廓线算法(转自http://blog.csdn.net/guognib/article/details/17045017?reload)
这个做法也类似啦...转移清楚明了,可见这种做法的优越性,总体复杂度O(Row*Col*(2^Col)*D)
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<vector> #include<string> #include<queue> #include<map> #include<set> #define REP(i, n) for(int i=0; i<n; i++) #define CLR(a, b) memset(a, b, sizeof(a)) #define PB push_back #define LL long long using namespace std; const int MOD = 1e9 + 7; int n, m; int C, D; int dp[2][1 << 10][22]; char ch[110][20]; int now, next; int ALL; void update(int nexts, int s, int nextk, int k) { if (nexts & (1 << m))///转移时的条件,必须保证转移后,之前的格子,都被覆盖 { dp[next][nexts ^ (1 << m)][nextk] += dp[now][s][k]; if (dp[next][nexts ^ (1 << m)][nextk] >= MOD) dp[next][nexts ^ (1 << m)][nextk] %= MOD; } } int main() { while (scanf("%d%d%d%d", &n, &m, &C, &D) != EOF) { REP(i, n) scanf("%s", ch[i]); CLR(dp, 0); now = 0; ALL = (1 << m) - 1; dp[0][ALL][0] = 1; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { next = now ^ 1; if (ch[i][j] == ‘0‘)/// 不可放时的转移 { for (int k = 0; k <= D; k++) for (int s = 0; s <= ALL; s++) if (dp[now][s][k]) update((s << 1) ^ 1, s, k, k); } else///可以放时的转移 { for (int k = 0; k <= D; k++) for (int s = 0; s <= ALL; s++) if (dp[now][s][k]) { update((s << 1), s, k, k);///此处不放,直接转移 update((s << 1) ^ 1, s, k + 1, k);///放1*1块的转移 if (j && !(s & 1)) update((s << 1) ^ 3, s, k, k);///横着放1*2块时的转移 if (i && !(s & (1 << (m - 1)))) update((s << 1) ^ (1 << m) ^ 1, s, k, k);///竖着放1*2块是的转移 } } CLR(dp[now], 0); now ^= 1; } } int ans = 0; for (int i = C; i <= D; i++) { ans += dp[now][ALL][i]; if (ans >= MOD) ans %= MOD; } printf("%d\n", ans); } }