http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1424
题意:给一面墙刷漆,要求刷下当前墙的时候,在其上面的墙必须都要已经刷完了。 求刷子换颜色的最少次数。
算法:状态DP。
状态转移方程:dp[i][j] = dp[k][j'] + 1。dp[i][j]表示在状态为j的情况下,最后一次刷的是i区域的最少转化次数。本题需要特别注意的就是需要考虑在当前状态下,最后一次刷i墙是否合法,即需要判断i墙上面的强是否都已经刷过了。其他的就和一般的状态DP一样了。
代码:
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; int N; struct Node{ int uly,ulx,lry,lrx ; int color ; friend bool operator <(const Node& a,const Node& b) { if(a.uly == b.uly) return a.ulx < b.ulx ; else return a.uly < b.uly ; } }block[16]; int dp[16][1<<15]; bool up(int k,int i) //判断k区域是否是在i区域的上方(即是否要求在刷i区域之前必须先刷完k区域) { if(block[k].lry<=block[i].uly) //注意:坐标原点是在左上角,因此在上面的要更小 { if(block[k].ulx>=block[i].lrx || block[k].lrx<=block[i].ulx) return false ; else return true; } else return false ; } bool is_ok(int j,int i) //判断在j状态下最后一次刷的是i区域是否合法(这里是可以优化的,可以通过预处理优化); { for(int k=1;k<=N;k++) { if(k==i) continue ; if(up(k,i)) { if(0 == (j&(1<<(k-1)))) return false ; } } return true; } int DP() { int new_s ; for(int i=1;i<=N;i++) { for(int j=0;j<(1<<N);j++) { dp[i][j] = 20 ; } } for(int j=0;j<(1<<N);j++) { for(int i=1;i<=N;i++) { new_s = (j&(1<<(i-1))); if(new_s==0 || !is_ok(j,i)) continue ; //考虑当前墙i是最后刷的是否合法 new_s = ( j^(new_s) ) ; if(new_s == 0) { dp[i][j] = 1; //相当于初始化 continue ; } int min_ = 20 ; for(int k=1;k<=N;k++) //寻找前一状态 { if(!is_ok(new_s,k)) continue ; if(block[k].color == block[i].color) { min_ = min(min_,dp[k][new_s]); } else min_ = min(min_,dp[k][new_s]+1) ; } dp[i][j] = min_ ; } } int min_ = 20 ; for(int i=1;i<=N;i++) { min_ = min(min_ ,dp[i][(1<<N)-1]) ; } return min_ ; } int main() { int T; //freopen("1in.txt","r",stdin); //freopen("1out.txt","w",stdout); scanf("%d",&T); while(T--) { scanf("%d",&N); for(int i=1;i<=N;i++) { scanf("%d %d %d %d %d",&block[i].uly,&block[i].ulx,&block[i].lry,&block[i].lrx,&block[i].color); } sort(block+1,block+N+1); int ans = DP(); printf("%d\n",ans); } return 0; }