【题解】平板涂色(洛谷 P1283)

题目

题目描述

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

为了涂色,APM 须要使用一组刷子。每一个刷子涂一种不一样的颜色 C i C_i 。APM 拿起一把有颜色 C i C_i 的刷子,并给全部颜色为 C i C_i 且符合下面限制的矩形涂色:在这里插入图片描述
为了不颜料渗漏使颜色混合,一个矩形只能在全部紧靠它上方的矩形涂色后,才能涂色。例如图中矩形 F F 必须在 C C D D 涂色后才能涂色。注意,每个矩形必须马上涂满,不能只涂一部分。node

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

输入格式

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

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

输出格式

一个整数,表示拿起刷子的最少次数。svg

输入输出样例

输入 #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 C i 20 1\le C_i \le 20 1 N 16 1\le N \le 16 spa

题解

转化题目

首先咱们须要把这个具体的图转化的抽象一点:.net

首先咱们定义每一条有向边:边链接的是一上一下两个靠紧的矩形,如图
在这里插入图片描述
转化为更简洁的图就成为了3d

在这里插入图片描述

很明显对于每个时刻,咱们都只能为没有边指向它的节点上色。固然,上完色后,此节点及他的全部出边都将被删除(相似于拓扑排序code

如今咱们跑一遍样例(不惟一)

首先咱们换上红色的刷子,刷去B和D

在这里插入图片描述

再换上蓝色的刷子,刷掉A,C,F,E
在这里插入图片描述

最后换上红色的刷子,删去G

代码讲解

这么小的数据范围,有两个暗示:1.状压DP 2.暴力搜索

正解好像有DP,可是我不会>﹏<,因此在这里就讲讲暴力的DFS吧,减减枝也慢不到哪去

1.存图

首先是转化部分,即把原图转化为方便DFS跑的图

注意题目的一个坑点:

一个矩形只能在全部紧靠它上方的矩形涂色后,才能涂色

注意这个这个"紧靠",划下来,要考

第一种状况: X a _ r > X b _ l Xa\_r>Xb\_l
在这里插入图片描述

第二种状况: X a _ l < X b _ r Xa\_l<Xb\_r 在这里插入图片描述
这两种状况都是须要在图中连边的(记为有前后关系)

为了后面的须要(判如图),咱们须要反向连边,由下连到上

代码:

scanf("%d",&n);
for(int i=1;i<=n;i++){
	scanf("%d %d %d %d %d",&a[i].xa,&a[i].ya,&a[i].xb,&a[i].yb,&a[i].color);
}
int m=0;
for(int i=1;i<=n;i++){
	for(int j=1;j<=n;j++){
		if(i==j) continue;
		if(a[i].xa==a[j].xb && !(a[j].yb<a[i].ya || a[j].ya>a[i].yb)){
			g[i].push_back(j);
		}
	}
}

2.搜索

每次搜索有3个状态:目前的总拿刷子数,当前枚举完第几个节点,目前刷子的颜色(以前涂的颜色)

若是目前已经枚举完第n个节点,则直接让ans与目前的总拿刷子数取min

不然枚举每个已经能够涂的节点(见下check),并搜索

dfs(sum+(last!=a[i].color),step+1,a[i].color);

这一步比较巧妙,特别是sum+(last!=a[i].color)

咱们知道last为以前的颜色,与a[i].color相同则last!=a[i].color等于0,也就是目前的总拿刷子数不用加一,不然目前的总拿刷子数就须要加一

代码

void dfs(int sum,int step,int last){
	if(sum>=ans) return;
	if(step==n){
		ans=min(ans,sum);
		return;
	}
	for(int i=1;i<=n;i++){
		if(!vis[i] && (check(i) || !a[i].xa)){
			vis[i]=1;
			dfs(sum+(last!=a[i].color),step+1,a[i].color);
			vis[i]=0;
		}
	}
}

3.判断是否可涂

这就体现咱们以前反向存图的重要性了,当一个节点的全部入度(反向后就是出度了)都被遍历,就能够认为这个节点已经可涂,可是算入度会不方便,因此咱们反向存图,算出度就OK了

bool check(int u){
	for(int i=0;i<g[u].size();i++){
		if(!vis[g[u][i]]) return false;
	}
	return true;
}

完整代码

#include <iostream>
#include <vector>
#include <queue>
using namespace std;
const int MAXN=20;
struct node{ 
	int xa,xb,ya,yb;
	int color;
}a[MAXN];
vector<int> g[MAXN];
int ans;
bool vis[MAXN];
bool check(int u){
	for(int i=0;i<g[u].size();i++){
		if(!vis[g[u][i]]) return false;
	}
	return true;
}
int n;
void dfs(int sum,int step,int last){
	if(sum>=ans) return;
	if(step==n){
		ans=min(ans,sum);
		return;
	}
	for(int i=1;i<=n;i++){
		if(!vis[i] && (check(i) || !a[i].xa)){
			vis[i]=1;
			dfs(sum+(last!=a[i].color),step+1,a[i].color);
			vis[i]=0;
		}
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d %d %d %d %d",&a[i].xa,&a[i].ya,&a[i].xb,&a[i].yb,&a[i].color);
	}
	int m=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(i==j) continue;
			if(a[i].xa==a[j].xb && !(a[j].yb<a[i].ya || a[j].ya>a[i].yb)){
				g[i].push_back(j);
			}
		}
	}
// for(int i=1;i<=n;i++){
// printf("%d:",i);
// for(int j=0;j<g[i].size();j++){
// printf("%d ",g[i][j]);
// }
// cout<<endl;
// }
	ans=0x3f3f3f3f;
	dfs(0,0,0);
	printf("%d",ans);
    return 0;
}

T h e The e n d end