WC2008观光游览【BZOJ2595】【斯坦纳树】
神奇的解法
其实这种表格的题目还可以写插头DP(•‾̑⌣‾̑•)✧˖°
(不会= =|||)
我们忽略刚刚的话题,说说这个斯坦纳树。
《斯坦纳树问题及其推广》说道:
斯坦纳树问题属于NP困难问题,因此看来不可能出现有效算法,即所谓的多项式算法来处理这一问题。目前文献中所提出的算法只不过是遍数法的种种改进,即在具体计算过程中,根据给定的实际问题,利用某些准则可以抛弃一部分情况不予考虑
也就是说,大部分的斯坦纳树问题都是构造的,没有一个特定的解法,我们学长还和我们抱怨说:考一些莫名其妙的题┑( ̄▽  ̄)┍
(闲话不多说,题解上)
题解
[纯暴力]
太暴力了这种办法,枚举每一个不是风景的格子按不安排志愿者 O((NM)!) 的复杂度,(我当时做练习就是用的这种),只有二十分
[YY]
用 SPFA , Dijkstra , Floyd ,对每两个点之间做一次最短路,再求一个最小生成树,但是这钟算法很好卡,当时就没写,结果有 60 分,( `)3’)▃▃▃▅▆▇▉
[插头DP]
不会。。。。
[斯坦纳树]
这个才是重点。
我们依然定义一个状态
f[i][j][s]
表示一棵树(显然,题目要求的那条路径一定是一棵树),以
(i,j)
为根,风景选择的集合为
s
(最多只有十个风景,用二进制来表示哪个点选了)的最短路径是多长,于是有下面的转移:
f[i][j][s]=f[i][j][s′]+f[i][j][s−s′]−val(i,j)(s′⊊s) (两棵树的合并,会有一个点算重了,要减掉,枚举 s′ 的时候, s′ 不能为 ∅ ,也不能等于 s )f[i][j][s′]=min(f[x][y][s]+val(i,j)) (( i,j) 从周围四个点转移过来,当然,如果 (i,j) 是个景点,那么, s′ 要比 s 多一个点)
预处理:
首先
[代码]
(大家帮我向 CSDN 提个建议✧ (≖ ‿ ≖)✧, markdown 的插入代码的格式太丑了,没有 html 的代码块那么好看,但是 markdown 好用些)
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
const int maxn = 13, maxm = 1035, INF = 0x7fffffff / 3, bai = 102501, shi = 1024;
const int dirx[] = {0, 0, 1, -1}, diry[] = {1, -1, 0, 0};
int n, m, k, M, map[maxn][maxn], f[maxn][maxn][maxm], fa[maxn][maxn][maxm], ord[maxn][maxn];
bool used[1200130], ans[maxn][maxn];
queue<int>Q;
int calc(int x, int y, int s) {return x * bai + y * shi + s;}
bool isSubet(int a, int b) {return (a | b) == a;}
void spfa(int sta) {
while(!Q.empty()) {
int u = Q.front(); Q.pop();
int x = u / bai, y = u % bai / shi;
for(int d = 0; d < 4; d++) {
int nx = x + dirx[d], ny = y + diry[d];
if(nx < 1 || ny < 1 || nx > n || ny > m) continue;
int ns = sta | ord[nx][ny];
if(f[nx][ny][ns] > f[x][y][sta] + map[nx][ny]) {
f[nx][ny][ns] = f[x][y][sta] + map[nx][ny];
fa[nx][ny][ns] = u;
int k = calc(nx, ny, ns);
if(!used[k] && sta == ns) {used[k] = true; Q.push(k);}
}
}
used[u] = false;
}
}
void initForOut(int x, int y, int sta) {
ans[x][y] = true;
int k = fa[x][y][sta];
if(!k) return;
int i = k / bai, j = k % bai / shi, s = k % bai % shi;
initForOut(i, j, s);
if(i == x && j == y) initForOut(i, j, sta - s);
}
void out() {
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++)
if(ans[i][j])
if(map[i][j]) putchar('o');
else putchar('x');
else putchar('_');
putchar('\n');
}
}
int main() {
freopen("trip.in", "r", stdin);
freopen("trip.out", "w", stdout);
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
for(int s = 0; s < 1024; s++) f[i][j][s] = INF;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++) {
scanf("%d", &map[i][j]);
f[i][j][0] = map[i][j];
if(!map[i][j]) f[i][j][1 << (k++)] = 0, ord[i][j] = 1 << (k - 1);
}
M = 1 << k;
for(int sta = 1; sta < M; sta++) {
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
for(int s = 1; s < sta; s++) {
if(isSubet(sta, s)) {
if(f[i][j][sta] > f[i][j][s] + f[i][j][sta - s] - map[i][j]) {
f[i][j][sta] = f[i][j][s] + f[i][j][sta - s] - map[i][j];
fa[i][j][sta] = calc(i, j, s);
}
}
}
if(f[i][j][sta] != INF) Q.push(calc(i, j, sta)), used[calc(i, j, sta)] = true;
}
}
spfa(sta);
}
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
if(!map[i][j]) {
printf("%d\n", f[i][j][M - 1]);
initForOut(i, j, M - 1);
out();
return 0;
}
return 0;
}