2595: [Wc2008]游览计划
Time Limit: 10 Sec Memory Limit: 256 MBSec Special JudgeSubmit: 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
0 1 1 0
2 5 5 1
1 5 5 1
0 1 1 0
Sample Output
xoox
___o
___o
xoox
HINT
对于100%的数据,N,M,K≤10,其中K为景点的数目。输入的所有整数均在[0,2^16]的范围内
Source
嗯。。裸的斯坦纳树模板
就是给一张图,要求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调了好久啊