P1283 平板涂色

P1283 平板涂色

题目描述

CE数码公司开发了一种名为自动涂色机(APM)的产品。它能用预约的颜色给一块由不一样尺寸且互不覆盖的矩形构成的平板涂色。ios

为了涂色,APM须要使用一组刷子。每一个刷子涂一种不一样的颜色C。APM拿起一把有颜色C的刷子,并给全部颜色为C且符合下面限制的矩形涂色:c++

为了不颜料渗漏使颜色混合,一个矩形只能在全部紧靠它上方的矩形涂色后,才能涂色。例如图中矩形F必须在C和D涂色后才能涂色。注意,每个矩形必须马上涂满,不能只涂一部分。数组

写一个程序求一个使APM拿起刷子次数最少的涂色方案。注意,若是一把刷子被拿起超过一次,则每一次都必须记入总数中。post

输入输出格式

输入格式:优化

 

第一行为矩形的个数N。下面有N行描述了N个矩形。每一个矩形有5个整数描述,左上角的y坐标和x坐标,右下角的y坐标和x坐标,以及预约颜色。spa

颜色号为1到20的整数。code

平板的左上角坐标老是(0, 0)。blog

坐标的范围是0..99。排序

N小于16。ci

 

输出格式:

 

输出至文件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
 

分析:

来自洛谷题解
 
 
一、

一看到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 }

 

 

二、

看到数据范围: 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 }

 

 

三、

用二进制压缩状态,一个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 }