CF152E:状态压缩

CF152E

题意:翻转'.'为'X',使所有的'X'都相连,并且使花费最少。

题解y

  • dp[x][y][z]表示点(x,y)分割状态z为两部分s1和s2,两部分都经过点(x,y),并且满足s1&s2 = 0,s1^s2 = z。也就是说s1和s2无交集,其并集为z。有k个点是必须要经过的,状态z表示经过哪几个点的状态。
  • 状态转移1:dp[x]][y][z] =min(dp[x][y][z],dp[x][y][s1] + dp[x][y][s2] - cost[x][y])。因为两者都经过(x,y)所以要减。
  • 状态转移2:点(x,y)向四个方向转移为dp[dx][dy][z],所以dp[dx][dy][z] = min(dp[dx][dy][dz],dp[x][y][z] + cost[dx][dy])
  • 每次进队列要打上标记,出队列要使标记清除,因为不一定是最优的。
  •  pre[x][y][z]表示点(x,y)分割状态z的上一个状态。用于回溯
  • 详细见代码。

代码

#include <bits/stdc++.h>
using namespace std;
int const inf = 0x7f7f7f7f;
int const N = 100 + 10;
int const M = 8;
int n,m,k;
int dir[4][2] = {0,1,1,0,0,-1,-1,0};
int cost[N][N],dp[N][N][1<<M];   //dp[x][y][z]表示点(x,y)分割状态为两部分
bool use[N][N],vis[N][N][1<<M];
struct Node
{
	int x,y,s;    //坐标和状态
	Node(){}
	Node(int x,int y,int s):x(x),y(y),s(s){};
}pre[N][N][1<<M];   //记录上一个状态
queue<Node>q;
void Init(){
	scanf("%d%d%d",&n,&m,&k);
	for(int i=0;i<n;i++)
		for(int j=0;j<m;j++)
			scanf("%d",&cost[i][j]);
	for(int i=0;i<n;i++){    //初始化
		for(int j=0;j<m;j++){
			for(int s=0;s<(1<<k);s++){
				dp[i][j][s] = inf;
				pre[i][j][s] = Node(-1,-1,-1);   //s = -1表示没有上一个状态
			}
		}
	}
	for(int i=0;i<k;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		x--,y--;   //输出从1开始的
		dp[x][y][(1<<i)] = cost[x][y];
		use[x][y] = true;  //变成水泥
		q.push(Node(x,y,1<<i));
		vis[x][y][(1<<i)] = true;	  //进入队列
	}
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++){
			if(!use[i][j]){
				dp[i][j][0] = cost[i][j];
				q.push(Node(i,j,0));
				vis[i][j][0] = true;
			}
		}
	}
}
void bfs(){
	while(!q.empty()){
		Node p = q.front();		q.pop();
		vis[p.x][p.y][p.s] = false;   //不一定是最优的
		for(int s=1;s<(1<<k);s++){
			if(p.s & s)	continue;
			int ss = p.s ^ s;   //把p.s和s中含有1合并    //
			if(dp[p.x][p.y][ss] > dp[p.x][p.y][p.s] + dp[p.x][p.y][s] - cost[p.x][p.y]){
				dp[p.x][p.y][ss] = dp[p.x][p.y][p.s] + dp[p.x][p.y][s] - cost[p.x][p.y];
				pre[p.x][p.y][ss] = Node(s,p.s,0);   //0表示加分割的点没有变化,但是两个状态合并
				if(vis[p.x][p.y][ss])	continue;
				vis[p.x][p.y][ss] = true;
				q.push(Node(p.x,p.y,ss));
			}
		}
		for(int i=0;i<4;i++){
			int dx = p.x + dir[i][0],	dy = p.y + dir[i][1];
			if(dx < 0 || dx >= n || dy < 0 || dy >= m)	continue;
			if(dp[dx][dy][p.s] > dp[p.x][p.y][p.s] + cost[dx][dy]){
				dp[dx][dy][p.s] = dp[p.x][p.y][p.s] + cost[dx][dy];
				pre[dx][dy][p.s] = Node(p.x,p.y,1);   //1表示加入一个点,分割的点变化
				if(vis[dx][dy][p.s])	continue;
				vis[dx][dy][p.s] = true;
				q.push(Node(dx,dy,p.s));
			}
		}
	}
}
void getpre(int x,int y,int s){
	if(pre[x][y][s].s == -1)	return;
	if(pre[x][y][s].s == 0){     
		getpre(x,y,pre[x][y][s].x);
		getpre(x,y,pre[x][y][s].y);
	}else{
		use[x][y] = true;
		getpre(pre[x][y][s].x,pre[x][y][s].y,s);
	}
}
void solve(){
	Node ans;
	int mx = inf;
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++){
			if(dp[i][j][(1<<k)-1] < mx){   //求出最优的方案
				mx = dp[i][j][(1<<k)-1];  
				ans = Node(i,j,(1<<k)-1); 
			}
		}
	}
	printf("%d\n",mx);
	getpre(ans.x,ans.y,ans.s);  //回溯
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++)
			printf("%c",use[i][j] ? 'X' : '.');
		printf("\n");
	}
}
int main(){
	Init();
	bfs();
	solve();
	return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值