1. 程式人生 > >【bzoj2595】[Wc2008]遊覽計劃 斯坦納樹

【bzoj2595】[Wc2008]遊覽計劃 斯坦納樹

輸入 ace include 選擇 重復 描述 字符數 string 初始

題目描述

給出一個N×M的方格圖,每個格子有自己權值,權值為0的格子已被選定。現要再選定一些格子,使得所有選定的格子(包括一開始已被選定的格子)四聯通,並且選定的格子的權值之和最小。輸出這個最小權值及一種可行方案。

輸入

第一行有兩個整數,N和 M,描述方塊的數目。
接下來 N行, 每行有 M 個非負整數, 如果該整數為 0, 則該方塊為一個景點;
否則表示控制該方塊至少需要的誌願者數目。 相鄰的整數用 (若幹個) 空格隔開,
行首行末也可能有多余的空格。

輸出

由 N + 1行組成。第一行為一個整數,表示你所給出的方案
中安排的誌願者總數目。
接下來 N行,每行M 個字符,描述方案中相應方塊的情況:

z ‘_’(下劃線)表示該方塊沒有安排誌願者;
z ‘o’(小寫英文字母o)表示該方塊安排了誌願者;
z ‘x’(小寫英文字母x)表示該方塊是一個景點;
註:請註意輸出格式要求,如果缺少某一行或者某一行的字符數目和要求不
一致(任何一行中,多余的空格都不允許出現) ,都可能導致該測試點不得分。

樣例輸入

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

樣例輸出

6
xoox
___o
___o
xoox


題解

斯坦納樹裸題

但是本題的斯坦納樹是點權,與大多數圖的邊權不同,下邊講解一般的邊權做法,然後再將本題的題解。

斯坦納樹:給出一些點,選出若幹條邊使得這些點連通,求總邊權的最值。

斯坦納樹是NP問題,不存在多項式時間內的解法,求解方法是狀壓dp。

設 $f[i][j]$ 表示選擇若幹條邊,使得狀態為 $i$ 的給定點連通,並且當前可以選擇下一條邊的端點為 $j$ 的最小邊權和。初始狀態 $f[2^k][pos[k]]=0$ ,其中 $pos[k]$ 為第 $k$ 個給定點的編號。

那麽我們對於每個 $i$ 和 $j$ ,首先枚舉 $i$ 的子集 $k$ ,用 $f[k][j]+f[i-k][j]$ 更新 $f[i][j]$ 。

然後再考慮同層轉移:如果 $x$ 與 $y$ 邊權為 $z$ ,用 $f[i][x]+z$ 更新 $f[i][y]$ ,用 $f[i][y]$ 更新 $f[i][x]$ 。容易發現這個轉移就是最短路,因此使用堆優化Dijkstra跑一遍得出所有的 $f[i][j]$ 。

最終答案就是 $min\{f[2^p-1][i]\}$

這個dp的理解比較顯然,時間復雜度 $O(3^p·n+2^p·m\log n)$ ,其中 $p$ 是給定點的數目。

那麽對於本題,由於權值在點上,因此枚舉子集轉移時,當前點會重復選擇,需要減去代價。

在同層轉移時,求的就是點權最短路,邊長為目標點的點權。

設 $f[i][j][k]$ 表示選定若幹條邊,使得狀態為 $i$ 的給定點連通,並且當前可以選擇下一條邊的相鄰點為 $(j,k)$ 的最小點權和。初始狀態 $f[2^k][posx[k]][posy[k]]=0$ .

首先枚舉子集轉移:$f[i][j][k]=min_{l\subseteq i}f[l][j][k]+f[i-l][j][k]-a[j][k]$ ,然後同層轉移,與 $(j,k)$ 相鄰的點的 $f$ 加上 $a[j][k]$ 可以轉移到 $f[i][j][k]$ 。

輸出方案的話直接記錄路徑,記錄從哪種方式的哪個狀態轉移過來,dfs一遍即可知道選定的點。

時間復雜度 $O(3^p·nm+2^p·nm\log n)$

#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N [1030][11][11]
using namespace std;
typedef pair<int , int> pr;
typedef pair<int , pr> ppr;
priority_queue<ppr> q;
int a[11][11] , px[11] , py[11] , f N , vis N , opt N , lx N , ly N , flag[11][11];
void dfs(int i , int x , int y)
{
	if(!f[i][x][y]) return;
	flag[x][y] = 1;
	if(opt[i][x][y]) dfs(i , lx[i][x][y] , ly[i][x][y]);
	else dfs(lx[i][x][y] , x , y) , dfs(ly[i][x][y] , x , y);
}
int main()
{
	int n , m , p = 0 , i , j , k , l , x , y , ans = 1 << 30;
	scanf("%d%d" , &n , &m);
	for(i = 1 ; i <= n ; i ++ )
	{
		for(j = 1 ; j <= m ; j ++ )
		{
			scanf("%d" , &a[i][j]);
			if(!a[i][j]) px[p] = i , py[p++] = j;
		}
	}
	memset(f , 0x3f , sizeof(f));
	for(i = 0 ; i < p ; i ++ ) f[1 << i][px[i]][py[i]] = 0;
	for(i = 1 ; i < (1 << p) ; i ++ )
	{
		for(j = 1 ; j <= n ; j ++ )
			for(k = 1 ; k <= m ; k ++ )
				for(l = i ; l ; l = i & (l - 1))
					if(f[i][j][k] > f[l][j][k] + f[i ^ l][j][k] - a[j][k])
						f[i][j][k] = f[l][j][k] + f[i ^ l][j][k] - a[j][k] , opt[i][j][k] = 0 , lx[i][j][k] = l , ly[i][j][k] = i ^ l;
		for(j = 1 ; j <= n ; j ++ )
			for(k = 1 ; k <= m ; k ++ )
				q.push(ppr(-f[i][j][k] , pr(j , k)));
		while(!q.empty())
		{
			x = q.top().second.first , y = q.top().second.second , q.pop();
			if(vis[i][x][y]) continue;
			vis[i][x][y] = 1;
			if(x > 1 && f[i][x - 1][y] > f[i][x][y] + a[x - 1][y]) f[i][x - 1][y] = f[i][x][y] + a[x - 1][y] , opt[i][x - 1][y] = 1 , lx[i][x - 1][y] = x , ly[i][x - 1][y] = y , q.push(ppr(-f[i][x - 1][y] , pr(x - 1 , y)));
			if(x < n && f[i][x + 1][y] > f[i][x][y] + a[x + 1][y]) f[i][x + 1][y] = f[i][x][y] + a[x + 1][y] , opt[i][x + 1][y] = 1 , lx[i][x + 1][y] = x , ly[i][x + 1][y] = y , q.push(ppr(-f[i][x + 1][y] , pr(x + 1 , y)));
			if(y > 1 && f[i][x][y - 1] > f[i][x][y] + a[x][y - 1]) f[i][x][y - 1] = f[i][x][y] + a[x][y - 1] , opt[i][x][y - 1] = 1 , lx[i][x][y - 1] = x , ly[i][x][y - 1] = y , q.push(ppr(-f[i][x][y - 1] , pr(x , y - 1)));
			if(y < m && f[i][x][y + 1] > f[i][x][y] + a[x][y + 1]) f[i][x][y + 1] = f[i][x][y] + a[x][y + 1] , opt[i][x][y + 1] = 1 , lx[i][x][y + 1] = x , ly[i][x][y + 1] = y , q.push(ppr(-f[i][x][y + 1] , pr(x , y + 1)));
		}
	}
	for(i = 0 ; i < p ; i ++ )
		if(ans > f[(1 << p) - 1][px[i]][py[i]])
			ans = f[(1 << p) - 1][px[i]][py[i]] , x = i;
	printf("%d\n" , ans);
	dfs((1 << p) - 1 , px[x] , py[x]);
	for(i = 1 ; i <= n ; i ++ )
	{
		for(j = 1 ; j <= m ; j ++ )
		{
			if(!a[i][j]) printf("x");
			else if(flag[i][j]) printf("o");
			else printf("_");
		}
		puts("");
	}
	return 0;
}

【bzoj2595】[Wc2008]遊覽計劃 斯坦納樹