P1283 平板涂色

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 }
时间: 2024-10-08 02:08:05

P1283 平板涂色的相关文章

【题解】 P1283 平板涂色

~先看题目内容:1. 给出了N的范围 N **< 16** and 0 < x,y < 1002. 颜色号为**不小于20的整数** 前者明示了我们这题可使用搜索做法 而非常有意思的是题目中并没有给出颜色号的具体数目 同时也说明了**存在存在颜色号1 与 3 却并不存在2的情况** 应该在代码中注意写法_(:зゝ∠)_ ------------ 不难构建出一个代码结构 用结构体存储各个矩形数据```cppstruct node{int lx, ly, rx, ry, color;} f[

平板涂色

CE数码公司开发了一种名为自动涂色机(APM)的产品.它能用预定的颜色给一块由不同尺寸且互不覆盖的矩形构成的平板涂色. 为了涂色,APM需要使用一组刷子.每个刷子涂一种不同的颜色C.APM拿起一把有颜色C的刷子,并给所有颜色为C且符合下面限制的矩形涂色: 为了避免颜料渗漏使颜色混合,一个矩形只能在所有紧靠它上方的矩形涂色后,才能涂色.例如图中矩形F必须在C和D涂色后才能涂色.注意,每一个矩形必须立刻涂满,不能只涂一部分. 写一个 1 #include<iostream> 2 #include&

POJ1691平板涂色

题目描述 原题来自:POJ 1691 CE 数码公司开发了一种名为自动涂色机(APM)的产品.它能用预定的颜色给一块由不同尺寸且互不覆盖的矩形构成的平板涂色. 为了涂色,APM 需要使用一组刷子.每个刷子蘸了颜色 C .APM 拿起一把蘸有颜色 C 的刷子,并给所有颜色为 C 的矩形涂色.请注意,涂色有顺序要求:为了避免颜料渗漏使颜色混合,一个矩形只能在所有紧靠它上方的矩形涂色后,才能涂色.例如图中矩形 F 必须在 C 和 DDD 涂色后才能涂色.注意,每一个矩形必须立刻涂满,不能只涂一部分.

[算法小练][图][拓扑排序+深度优先搜索] 平板涂色问题

说在前面 本题是一道经典题目,多做经典题目可以节省很多学习时间,比如本题就包含了许多知识:回溯+剪枝+拓扑排序+深度优先搜索.[动态规划方法另作讨论] 关键代码 题: CE数码公司开发了一种名为自动涂色机(APM)的产品.它能用预定的颜色给一块由不同尺寸且互不覆盖的矩形构成的平板涂色. 为了涂色,APM需要使用一组刷子.每个刷子涂一种不同的颜色C.APM拿起一把有颜色C的刷子,并给所有颜色为C且符合下面限制的矩形涂色: 为了避免颜料渗漏使颜色混合,一个矩形只能在所有紧靠它上方的矩形涂色后,才能涂

#10023. 「一本通 1.3 练习 2」平板涂色

#include<bits/stdc++.h> #define lop(x,m,n) for(int x=m;x<=n;x++) using namespace std; int n; struct node{ int x1,y1,x2,y2,se; }sq[20]; int used[20]={0}; int maxx,maxy; bool b[40][40]={0}; void init() { maxx=-1,maxy=-1; scanf("%d",&n

BZOJ 1260 [CQOI2007]涂色paint(区间DP)

[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1260 [题目大意] 假设你有一条长度为n的木版,初始时没有涂过任何颜色 每次你可以把一段连续的木版涂成一个给定的颜色,后涂的颜色覆盖先涂的颜色 求最少的涂色次数达到目标状态 [题解] dp[i][j]表示涂抹i到j的最优答案, 显然当i和j相同时,可以从i+1……j,i……j-1,i+1……j-1转移过来, 同时也可以从两个区间组合得到. [代码] #include <cstdio>

POJ 1129 Channel Allocation(暴力搜--涂色问题)

Channel Allocation Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 13295   Accepted: 6806 Description When a radio station is broadcasting over a very large area, repeaters are used to retransmit the signal so that every receiver has a s

BZOJ 1260: [CQOI2007]涂色paint( 区间dp )

区间dp.. dp( l , r ) 表示让 [ l , r ] 这个区间都变成目标颜色的最少涂色次数. 考虑转移 : l == r 则 dp( l , r ) = 1 ( 显然 ) s[ l ] == s[ l + 1 ] 则 dp( l , r ) = dp( l + 1 , r )     s[ r ] == s[ r - 1 ] 则 dp( l , r ) = dp( l , r - 1 )  因为只要在涂色时多涂一格就行了, 显然相等 , 所以转移一下之后更好做 s[ l ] == s

HDU 4365 正方形格子涂色中心对称轴对称的涂法有多少种-思维-(矩阵坐标关系&amp;快速幂取模)

题意:n*n的格子,涂色,有k种颜料,必须满足旋转任意个90度和翻转之后图片的样子不变,现在已经有m个格子涂过色了,问还有多少种涂法满足上述条件. 分析: 满足上述对称条件,那么涂色的种类问题我们可以放在正方形的一个角来做,因为一个角确定了其他角的颜色也就确定了. 以左上角的下半角为例.共有1+2+....+(n+1)/2个格子,然后记录涂过色的格子对应到这个三角形里的格子数目,用tot来记录,即每输入一个涂过色的格子的坐标我们就在这个三角形里找与之对应的坐标,用vis[][]数组标记是否已经计