题解:P1283 平板涂色
lovely_aris · · 题解
状压 DP,思路:
不知道怎么直接预处理各种各样的条件,只能一点一点在循环里写了。 这些可能对找 bug 和没考虑到的情况有用。
给出几个区块,我们在涂色时需要把与它本身上方有重叠的区块涂色,首行没有前置区块,我们要求最少换笔数,注意不是落笔数,只要是相同颜色的画笔,可以在任意符合规则的位置,即无需区块相邻,都可以用一笔完成。
题目要求需要前置区块完成才可进行这一块,所以想到拓扑,我们用链式前向星来存前置区块。同时我用结构体来存区块。
再看颜色要求
对于 dp 我们记
状态转移:我们找一个存在于状态
这就是整体思路。接下来还有我自己的一些判断条件,我会在代码里用注释体现。
这些花了一个下午。
接下来是代码。
#include<bits/stdc++.h>
using namespace std;
int in(){
int ans=0,f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
for(;isdigit(c);c=getchar()) ans=ans*10+c-'0';
return ans*f;
}//快读
struct F{
int xl,xr,yu,yd,col;
}sq[50];//区块
int n,m;
int fir[1010],ne[1010],en[1010],cnt;//链式前向星
void add(int u,int v){
cnt++;
ne[cnt]=fir[u];
en[cnt]=v;
fir[u]=cnt;
}
int f[100010][50],d[1010];
bool check(int s,int p){//判断当状态s时,区块p是否可以画
for(int i=fir[p];i;i=ne[i]){
int u=en[i];
if(((1<<(u-1))&s)==0) return 0;
}
return 1;
}
bool cd(int i,int j){//判断区块i,j是否有重叠
int xl1=sq[i].xl,xr1=sq[i].xr;
int xl2=sq[j].xl,xr2=sq[j].xr;
if(xr1<=xl2) return 0;
if(xr2<=xl1) return 0;
return 1;
}
bool ch_col(int s,int p,int col){
//判断当状态s时 当前区块为p s的上一个状态(去除p)的落笔颜色中是否包含col
//如果没有就不能遍历
if(d[p]==0&&s==(1<<(p-1))) return 1;//如果是无前置区块,则可以直接落笔
s-=(1<<(p-1));
for(int i=1;i<=n;i++){
if(s&(1<<(i-1))){
if(col==sq[i].col) return 1;
}
}
return 0;
}
int main(){
// freopen("data.in","r",stdin);
n=in();
for(int i=1;i<=n;i++){
sq[i].yu=in();
sq[i].xl=in();
sq[i].yd=in();
sq[i].xr=in();
sq[i].col=in();
m=max(m,sq[i].col);//最大颜色
}
//找自己的前置区块
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(sq[i].yd==sq[j].yu && i!=j){
if(cd(i,j)){//i是j的前置区块
add(j,i);
d[j]++;//记录有多少个前置区块
}
}
}
}
//状压dp
memset(f,0x3f,sizeof(f));
for(int i=1;i<=m;i++) f[0][i]=1;
for(int i=1;i<(1<<n);i++){
for(int j=1;j<=n;j++){
int to=i-(1<<(j-1));//前一个状态to
if(check(to,j) && ((i&(1<<(j-1)))!=0)){//j在i状态中且j的前置区块完成
for(int k=1;k<=m;k++){
if(sq[j].col==k && (ch_col(i,j,k))){//颜色相同 且颜色存在于状态i中
f[i][sq[j].col]=min(f[i][sq[j].col],f[to][k]);
}
else f[i][sq[j].col]=min(f[i][sq[j].col],f[to][k]+1);
}
}
}
}
int ans=0x7fffffff;
for(int i=1;i<=m;i++){
ans=min(ans,f[(1<<n)-1][i]);
}//将所有可能落笔颜色遍历
cout<<ans;
return 0;
}