2595: [Wc2008]游览计划

2595: [Wc2008]游览计划

Time Limit: 10 Sec   Memory Limit: 256 MBSec   Special Judge
Submit: 1312   Solved: 602
[ Submit][ Status][ Discuss]

Description

Input

第一行有两个整数,N和 M,描述方块的数目。 
接下来 N行, 每行有 M 个非负整数, 如果该整数为 0, 则该方块为一个景点;
否则表示控制该方块至少需要的志愿者数目。 相邻的整数用 (若干个) 空格隔开,
行首行末也可能有多余的空格。

Output


由 N + 1行组成。第一行为一个整数,表示你所给出的方案
中安排的志愿者总数目。 
接下来 N行,每行M 个字符,描述方案中相应方块的情况: 
z  ‘_’(下划线)表示该方块没有安排志愿者; 
z  ‘o’(小写英文字母o)表示该方块安排了志愿者; 
z  ‘x’(小写英文字母x)表示该方块是一个景点; 
注:请注意输出格式要求,如果缺少某一行或者某一行的字符数目和要求不
一致(任何一行中,多余的空格都不允许出现) ,都可能导致该测试点不得分。

Sample Input

4 4
0 1 1 0
2 5 5 1
1 5 5 1
0 1 1 0



Sample Output

6
xoox
___o
___o
xoox

HINT

 对于100%的数据,N,M,K≤10,其中K为景点的数目。输入的所有整数均在[0,2^16]的范围内

Source

[ Submit][ Status][ Discuss]



嗯。。裸的斯坦纳树模板

就是给一张图,要求k个关键点联通的最小生成树的权值最小值是多少

这就意味着原图有些点可能根本就是多余的

那么将题目给的图转换一下,然后:

f[o][j]:关键点的选择状态为o,当前以j为根,最优方案

f[o][j] = min(f[op][j] + f[o-op][j])

即将已经构建好的两棵子树直接合并

或f[o][j] = min(f[o][k] + va[j])  即某个合法方案中再连接上k-j这条边

第二个式子很像最短路的式子?

没错,用SPFA解决


tip:

枚举一个二进制数的真子集 for(int op = (o-1)&o; op; op = (op-1)&o)

SPFA的源点只选用f[o][j] != INF


#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<stack>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<bitset>
using namespace std;

const int maxn = 210;
const int maxm = 1<<12;
const int INF = 1E8;
const int dx[8] = {0,1,0,-1,1,1,-1,-1};
const int dy[8] = {1,0,-1,0,-1,1,-1,1};

struct data{
	int f1,t1,f2,t2;
	data(int _f1 = 0,int _t1 = 0,int _f2 = 0,int _t2 = 0) {
		f1 = _f1; t1 = _t1; f2 = _f2; t2 = _t2;
	}
}fa[maxm][maxn];

int n,m,tot,K,num[maxn][maxn],va[maxn],f[maxm][maxn],
	typ[maxn][maxn],vis[maxn],pox[maxn],poy[maxn],p[maxn][maxn];

queue <int> Q;
vector <int> v[maxn];

void SPFA(int o)
{
	while (!Q.empty()) {
		int k = Q.front(); Q.pop(); vis[k] = 0;
		for (int i = 0; i < v[k].size(); i++) {
			int to = v[k][i];
			if (f[o][to] > f[o][k] + va[to]) {
				f[o][to] = f[o][k] + va[to];
				fa[o][to] = data(o,k,maxm,to);
				if (!vis[to]) vis[to] = 1,Q.push(to);
			}
		}
	}
}

bool check(data k)
{
	if (k.f1) return 1;
	if (k.t1) return 1;
	if (k.f2) return 1;
	if (k.t2) return 1;
	return 0;
}

void Work(int o,int i)
{
	data k = fa[o][i];
	if (!check(k)) return;
	Work(k.f1,k.t1);
	if (k.f2 == maxm) typ[pox[k.t2]][poy[k.t2]] = 1;
	else Work(k.f2,k.t2);
}

void Solve(int o)
{
		for (int j = 1; j <= tot; j++) {
			for (int op = o & (o - 1); op; op = (op - 1) & o) 
				if (f[op][j] + f[o-op][j] - va[j] < f[o][j]) {
					f[o][j] = f[op][j] + f[o-op][j] - va[j];
					fa[o][j] = data(op,j,o-op,j);
				}
			if (f[o][j] != INF) Q.push(j),vis[j] = 1;	
		} 
		SPFA(o);
}

int main()
{
	#ifdef DMC
		freopen("DMC.txt","r",stdin);
	#endif
	
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++) {
			scanf("%d",&p[i][j]);
			if (!p[i][j]) {
				num[i][j] = ++tot;
				pox[tot] = i; poy[tot] = j;
				va[tot] = p[i][j];
			}
		}
	K = tot;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			if (p[i][j]) {
				num[i][j] = ++tot;
				pox[tot] = i; poy[tot] = j;
				va[tot] = p[i][j];
			}
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++) 
			for (int l = 0; l < 4; l++) {
				int xx = i + dx[l];
				int yy = j + dy[l];
				if (xx == 0 || xx > n || yy == 0 || yy > m) continue;
				v[num[i][j]].push_back(num[xx][yy]);
			}
	
	for (int i = 0; i < (1<<K); i++)
		for (int j = 1; j <= tot; j++)
			f[i][j] = INF,fa[i][j] = data(0,0,0,0);
	for (int i = 0; i < K; i++) f[1<<i][i+1] = 0;
	
	for (int o = 0; o < (1<<K); o++) {
		Solve(o);
	} 
	
	int ans = ~0U>>1,root;
	for (int i = 1; i <= tot; i++) 
		if (f[(1<<K)-1][i] < ans)
			ans = f[(1<<K)-1][i],root = i;
	Work((1<<K)-1,root);
	printf("%d\n",ans);
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			if (!p[i][j]) printf("x");
			else if (typ[i][j]) printf("o");
			else printf("_");
		}
		printf("\n");
	}
	return 0;
}

最后。。。。因为本题的权值在点上不在边上

所以上述的第一个方程其实是错的

应为f[o][j] = f[op][j] + f[o-op][j] - va[j]

因为根的权值被选了两次。。两次

MLGB调了好久啊

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值