P1283 平板涂色
题目描述
CE数码公司开发了一种名为自动涂色机(APM)的产品。它能用预定的颜色给一块由不同尺寸且互不覆盖的矩形构成的平板涂色。
为了涂色,APM需要使用一组刷子。每个刷子涂一种不同的颜色C。APM拿起一把有颜色C的刷子,并给所有颜色为C且符合下面限制的矩形涂色:
为了避免颜料渗漏使颜色混合,一个矩形只能在所有紧靠它上方的矩形涂色后,才能涂色。例如图中矩形F必须在C和D涂色后才能涂色。注意,每一个矩形必须立刻涂满,不能只涂一部分。
写一个程序求一个使APM拿起刷子次数最少的涂色方案。注意,如果一把刷子被拿起超过一次,则每一次都必须记入总数中。
输入输出格式
输入格式:
第一行为矩形的个数N。下面有N行描述了N个矩形。每个矩形有5个整数描述,左上角的y坐标和x坐标,右下角的y坐标和x坐标,以及预定颜色。
颜色号为1到20的整数。
平板的左上角坐标总是(0, 0)。
坐标的范围是0..99。
N小于16。
输出格式:
输出至文件paint.out,文件中记录拿起刷子的最少次数。
输入输出样例
输入样例#1:
7 0 0 2 2 1 0 2 1 6 2 2 0 4 2 1 1 2 4 4 2 1 4 3 6 1 4 0 6 4 1 3 4 6 6 2
输出样例#1:
3
分析:
来自洛谷题解
1、
一看到n<=16,颜色<=20,马上就要想到裸的状压dp,
设dp[S][i]表示在集合S(已经涂的矩形的集合)中,最后涂色的颜色是i,所需的最少拿刷子的次数。至于集合,就是用二进制表示。
先预处理出每个矩形上面有哪些矩形,这个由于数据范围比较小,都不用离散化,直接开个二维数组弄个矩形覆盖就行了。
至于具体的Dp,应该还算是比较好写的。
枚举一下最后一次涂的是第j个矩形,而第j个矩形的颜色是col[j],当然,这个第j个矩形必须满足两个限制:
1.j属于S
2.j上面的矩形都属于S
那么Dp(S,col[j])=min(Dp(S-(1<<(j-1)),k)+1){枚举另一个颜色k,并且k!=col[j]}
Dp(S,col[j])=min(Dp(S-(1<<(j-1)),col[j]))
这个还是很好理解的。
参考代码:
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 using namespace std; 5 template<class T>void ChkMin(T &a,T b){if (a>b)a=b;} 6 const int INF=0x3f3f3f3f; 7 const int N=101; 8 const int M=21; 9 int lx[M],size[M],ly[M],col[M],rx[M],ry[M]; 10 int n,a[N][N],dp[1<<16+1][M],up[M][M]; 11 inline bool in(int i,int S){ 12 return (S>>(i-1))&1; 13 } 14 inline bool ok(int i,int S){ 15 bool flag=true; 16 for (int j=1;j<=size[i] && flag;j++)flag&=in(up[i][j],S); 17 return flag; 18 } 19 int main(){ 20 scanf("%d",&n); 21 for (int i=1;i<=n;i++){ 22 scanf("%d%d%d%d%d",&lx[i],&ly[i],&rx[i],&ry[i],&col[i]); 23 for (int x=lx[i];x<rx[i];x++) 24 for (int y=ly[i];y<ry[i];y++) 25 a[x][y]=i; 26 } 27 for (int i=1;i<=n;i++){ 28 if (!lx[i])continue; 29 lx[i]--; 30 for (int j=ly[i]+1;j<=ry[i];j++) 31 if (a[lx[i]][j]!=a[lx[i]][j-1])up[i][++size[i]]=a[lx[i]][j-1]; 32 if (a[lx[i]][ry[i]]==a[lx[i]][ry[i]-1])up[i][++size[i]]=a[lx[i]][ry[i]-1]; 33 } 34 memset(dp,0x3f,sizeof(dp)); 35 for (int i=1;i<=20;i++) 36 dp[0][i]=1; 37 for (int i=1;i<(1<<n);i++){ 38 for (int j=1;j<=n;j++) 39 if (in(j,i) && ok(j,i)){ 40 for (int k=1;k<=20;k++) 41 if (k!=col[j])ChkMin(dp[i][col[j]],dp[i-(1<<(j-1))][k]+1); 42 ChkMin(dp[i][col[j]],dp[i-(1<<(j-1))][col[j]]); 43 } 44 } 45 int ans=INF; 46 for (int i=1;i<=20;i++) 47 ChkMin(ans,dp[(1<<n)-1][i]); 48 printf("%d",ans); 49 return 0; 50 }
2、
看到数据范围: n < 16 ?直接搜索,但是应该要剪枝
搜索思路
读入数据,统计颜色,然后每个颜色都试一遍,即把该颜色的且能涂的砖涂上。
下一次涂色不能涂上次涂过的色。涂完了记录结果
为了不超时,加了两个剪枝
- 最优化剪枝:当前涂色次数大于等于当前答案,直接退出(这个好理解吧)
- 可行性剪枝:如果当前没有涂到一个砖,直接退出(如果接着搜,会多一个次数,可能还会死循环,,,)
至于判断该砖是否能涂,先预处理,把紧邻该砖上方的砖用数组记录下来,再判断那些砖是否被涂
代码如下(格式丑勿喷)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #include<cstdlib> 7 using namespace std; 8 struct lbq //结构体 a1b1 该砖左上角坐标 a2b2 右下角坐标 x 颜色 9 { 10 int a1,b1,a2,b2,x; 11 }a[20]; 12 int ccmp(lbq a,lbq b) 13 { 14 if(a.a1!=b.a1) return a.a1<b.a1; 15 return a.b1<b.b1; 16 } 17 bool d=false; 18 int de[20]={0};//de数组表示是否有该颜色 19 int n,m,ans=999,b[20],fk[20][20]; //b数组代表该砖是否被涂 fk[i][j]表示第i个砖是否紧邻上方第j个砖 m 最大颜色编号 20 bool OK(int o) 21 { 22 for(int i=1;i<=n;i++) 23 if(fk[o][i]&&!b[i]) return false; //如果i砖下面紧邻o,但i没涂过,返回false 24 return true; 25 } 26 void dfs(int o,int pq,int xx) //o 涂色次数 pq 涂过颜色的砖 xx 上次涂的颜色 27 { 28 if(o>=ans) return; //当前涂色次数大于等于当前答案,直接退出 29 if(pq==n) //涂完了,记录答案 30 { 31 ans=o; 32 return; 33 } 34 for(int i=1;i<=m;i++) //枚举颜色 35 { 36 int qq=0; //代表现在用这个颜色涂的砖数 37 if(i!=xx&&de[i])//如果有这个颜色,并且这种颜色上次没用过 38 { 39 for(int j=1;j<=n;j++) //涂色 40 { 41 if(!b[j]&&a[j].x==i&&OK(j)) //如果没涂过该砖,并且能涂 42 { 43 b[j]=1; 44 qq++; 45 } 46 else if(b[j]&&a[j].x==i) b[j]++; 47 } 48 if(qq>0) dfs(o+1,pq+qq,i); 如果涂了砖,进行下一步 49 for(int j=n;j>=1;j--) //回溯一步 50 { 51 if(b[j]==1&&a[j].x==i&&OK(j)) 52 { 53 b[j]=0; 54 qq--; 55 } 56 else if(b[j]>1&&a[j].x==i) b[j]--; 57 } 58 } 59 } 60 } 61 int main() 62 { 63 cin>>n; 64 for(int i=1;i<=n;i++) 65 { 66 scanf("%d%d%d%d%d",&a[i].a1,&a[i].b1,&a[i].a2,&a[i].b2,&a[i].x); 67 a[i].a1++; //个人习惯把左上角坐标+1,就可以看成它左上角所占的方格 68 a[i].b1++; // 例如 0 0 2 2 +1后为 1 1 2 2 ,表示该砖左上角,右下角所占的方格 69 de[a[i].x]++; //记录颜色 70 } 71 for(int i=1;i<=20;i++) if(de[i]) m=i; //求最大颜色编号 72 sort(a+1,a+n+1,ccmp); //按左上角坐标大小从小到大排序(先考虑纵,再考虑横) 73 for(int i=2;i<=n;i++) 74 for(int j=i-1;j>=1;j--) 75 if(a[i].a1==a[j].a2+1&& ( (a[i].b1>=a[j].b1&&a[i].b1<=a[j].b2) || (a[i].b2>=a[j].b1&&a[i].b2<=a[j].b2) ) ) 76 fk[i][j]=1; //如果i砖的最上面紧邻j砖最下面,且两砖横坐标有重叠部分,即j砖为i砖紧邻上面的砖 77 dfs(0,0,0); 78 cout<<ans;//结果 79 return 0; 80 }
3、
用二进制压缩状态,一个n位二进制数的第i位为0或1表示第i块板是否图上了色。
f[A][i]表示达到A状态,最后一次涂色的颜色是i的最少换颜色次数。
位运算不懂的自己百度。
检查二进制数A第i位是否为0: A&(1<<(i-1))==(1<<(i-1))
二进制数A的第i位上的1变为0后的数: A-(1<<(i-1))
详见代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define file(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout) 4 int f[(1<<16)+1][21],n,color[20],b[100][100],maxcolor=0; 5 int num[20],temp[20][20],xx[20],xy[20],yx[20],yy[20],ans=INT_MAX; 6 bool check(int A,int x)//检查A状态下第x个矩形上方的所有矩形是否已经涂完色 7 { 8 bool flag=true; 9 for(int i=1;i<=num[x]&&flag;i++)//num[x]是第x个矩形上方相邻的矩形个数,temp[x][i]是这些矩形的编号 10 if(((1<<(temp[x][i]-1))&A)!=(1<<(temp[x][i]-1)))flag=false;//如果上方某个矩形还未被涂色,则第x个矩形就不能涂色,就标记flag为false 11 return flag;//如果上方没有矩形则num[x]==0,就不会进行循环,直接return ture 12 } 13 int main() 14 { 15 //file(paint); 16 memset(f,32,sizeof f);//初始化 17 scanf("%d",&n); 18 for(int i=1;i<=n;i++) 19 { 20 scanf("%d%d%d%d%d",&xx[i],&yx[i],&xy[i],&yy[i],&color[i]); 21 if(color[i]>maxcolor)maxcolor=color[i];//记录颜色的个数,即最大颜色编号 22 for(int j=xx[i];j<xy[i];j++) 23 for(int k=yx[i];k<yy[i];k++) 24 b[j][k]=i;//b[i][j]表示第i行j列所在的矩形编号 25 } 26 for(int i=1;i<=n;i++) 27 { 28 int k=xx[i]-1;//扫一遍矩形上面一行,num[i]表示矩形i上方的不同矩形个数,temp[i][j]表示第i个矩形上方第j个矩形的编号。 29 if(k<0)continue; 30 for(int j=yx[i];j<yy[i];)//从左往右扫上方的矩形 31 if(b[k][j])//如果有矩形 32 { 33 int l=j; 34 while(b[k][l]==b[k][j]&&l<yy[i])l++;//跳过编号相同的矩形 35 temp[i][++num[i]]=b[k][j];//记录上方的矩形编号 36 j=l;//继续扫 37 } 38 } 39 for(int i=1;i<=maxcolor;i++) 40 f[0][i]=1;//初始化,所有平板未涂色时需要拿一次刷子。 41 for(int A=1;A<=((1<<n)-1);A++)//枚举每个着色状态,n块平板的状态用二进制表示就是0到(2^n-1),位运算优化 42 for(int i=1;i<=n;i++)//枚举放第i块平板 43 if(((1<<(i-1))&A)==(1<<(i-1))&&check(A,i))//检查该状态中是否已经涂上了第i个矩形,还有该状态下第i个矩形的上方矩形是否都已涂完 44 {//状态转移: for(int j=1;j<=maxcolor;j++)//枚举每种颜色 45 46 if(j!=color[i])f[A][color[i]]=min(f[A][color[i]],f[A-(1<<(i-1))][j]+1);//如果前驱状态的颜色不同就要换刷子 47 48 f[A][color[i]]=min(f[A][color[i]],f[A-(1<<(i-1))][color[i]]);//颜色相同就不换刷子 49 50 } 51 for(int i=1;i<=maxcolor;i++) 52 ans=min(ans,f[(1<<n)-1][i]);//枚举最后颜色不同的最终状态,取最小值为结果 53 printf("%d\n",ans); 54 return 0; 55 }