zoukankan      html  css  js  c++  java
  • @bzoj


    @description@

    从未来过绍兴的小D有幸参加了Winter Camp 2008,他被这座历史名城的秀从未来过绍兴的小D有幸参加了Winter Camp 2008,他被这座历史名城的秀丽风景所吸引,强烈要求游览绍兴及其周边的所有景点。

    主办者将绍兴划分为N行M列(NXM)个方块,景点含于方块内,且一个方块至多有一个景点。无景点的方块视为路。

    为了保证安全与便利,主办方依据路况和治安状况,在非景点的一些方块内安排不同数量的志愿者:在景点内聘请导游(导游不是志愿者)。在选择旅游方案时,保证任意两个景点之间,存在一条路径,在这条路径所经过的每一个方块都有志愿者或者该方块为景点。既能满足选手们游览的需要,又能够让志愿者的总数最少。

    现在,希望你能够帮助主办方找到一种最好的安排方案。

    原题传送门。

    @solution@

    斯坦纳树经典题。

    斯坦纳树的定义是:给定一个图与 k 个特殊点,求将这 k 个特殊点连通的最小生成树。
    当 k = 点数时,就是常见的最小生成树的定义。

    状压 dp:记 dp[i][s] 表示当前点为 i,已经包含了 k 个特殊点的集合为 s,最小连通代价。
    第一类转移:dp[i][s] = min{dp[i][t] + dp[i][s xor t]},即两个集合取并集。
    第二类转移:dp[i][s] = min{dp[j][s] + dis(i, j)},即任意加一条边。
    正确性很显然,因为每一种可行方案都会被上面的过程考虑到。

    第二类转移带环,但是注意到这个转移很像最短路的 “松弛” 操作,于是我们直接写个 spfa 即可。
    时间复杂度(如果把 spfa 算作 O(nm) 的上界的话)为 O(n*3^k + nm*2^k)。

    本题因为是点权,为了不使第一种转移出现重复,dp[i][s] 不包含 i 的点权。

    @accepted code@

    #include <queue>
    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    
    typedef pair<int, int> pii;
    #define mp make_pair
    #define fi first
    #define se second
    
    const int INF = (1 << 30);
    const int dx[] = {0, 1, -1, 0, 0};
    const int dy[] = {0, 0, 0, -1, 1};
    
    bool inq[10][10];
    int A[10][10], id[10][10], N, M, K, S;
    int dp[10][10][1 << 10], pre[10][10][1 << 10];
    queue<pii>que;
    void run(int s) {
    	for(int i=0;i<N;i++)
    		for(int j=0;j<M;j++)
    			que.push(mp(i, j)), inq[i][j] = true;
    	while( !que.empty() ) {
    		pii p = que.front(); que.pop(); inq[p.fi][p.se] = false;
    		for(int i=1;i<=4;i++) {
    			int x0 = p.fi + dx[i], y0 = p.se + dy[i];
    			if( x0 >= N || y0 >= M || x0 < 0 || y0 < 0 ) continue;
    			if( dp[x0][y0][s] > dp[p.fi][p.se][s] + A[p.fi][p.se] ) {
    				dp[x0][y0][s] = dp[p.fi][p.se][s] + A[p.fi][p.se];
    				pre[x0][y0][s] = i;
    				if( !inq[x0][y0] ) {
    					que.push(mp(x0, y0));
    					inq[x0][y0] = true;
    				}
    			}
    		}
    	}
    }
    void solve() {
    	for(int s=1;s<S;s++) {
    		for(int i=0;i<N;i++)
    			for(int j=0;j<M;j++) {
    				int t = s & (s - 1);
    				while( t ) {
    					if( dp[i][j][s] > dp[i][j][t] + dp[i][j][s ^ t] ) {
    						dp[i][j][s] = dp[i][j][t] + dp[i][j][s ^ t];
    						pre[i][j][s] = -t;
    					}
    					t = s & (t - 1);
    				}
    			}
    		run(s);
    	}
    }
    bool tag[10][10];
    void get(int x, int y, int s) {
    	tag[x][y] = true;
    	if( pre[x][y][s] > 0 )
    		get(x - dx[pre[x][y][s]], y - dy[pre[x][y][s]], s);
    	else if( pre[x][y][s] < 0 ) {
    		pre[x][y][s] = -pre[x][y][s];
    		get(x, y, pre[x][y][s]), get(x, y, s^pre[x][y][s]);
    	}
    }
    
    int main() {
    	scanf("%d%d", &N, &M);
    	for(int i=0;i<N;i++)
    		for(int j=0;j<M;j++) {
    			scanf("%d", &A[i][j]);
    			if( A[i][j] == 0 ) id[i][j] = (K++);
    		}
    	S = (1 << K);
    	for(int i=0;i<N;i++)
    		for(int j=0;j<M;j++) {
    			for(int s=0;s<S;s++) dp[i][j][s] = INF;
    			if( A[i][j] == 0 ) dp[i][j][1 << id[i][j]] = 0;
    		}
    	solve();
    	int x, y, ans = INF;
    	for(int i=0;i<N;i++)
    		for(int j=0;j<M;j++)
    			if( ans > dp[i][j][S - 1] + A[i][j] )
    				ans = dp[i][j][S - 1] + A[i][j], x = i, y = j;
    	printf("%d
    ", ans);
    	get(x, y, S - 1);
    	for(int i=0;i<N;i++) {
    		for(int j=0;j<M;j++) {
    			if( tag[i][j] ) {
    				if( A[i][j] == 0 )
    					putchar('x');
    				else putchar('o');
    			}
    			else putchar('_');
    		}
    		puts("");
    	}
    }
    /*
    8 8
    1 4 1 3 4 2 4 1
    4 3 1 2 0 1 2 3
    3 2 1 3 0 3 1 2
    2 6 5 0 2 4 1 0
    5 1 2 1 3 4 2 5
    5 1 3 1 5 0 1 4
    5 0 6 1 4 5 3 4
    0 2 2 2 3 4 1 1
    */
    

    @details@

    关于第二类转移,其实也可以用 floyd 先预处理出所有点对的最短路,然后直接点对两两 O(n^2) 转移。

    因为 spfa 的复杂度上界是 O(NM),对于 M 比较大的稠密图用 floyd 更快。

    当然对于本题这种网格图 spfa 的复杂度和 floyd 没差,spfa 的常数说不定还要小一些。

  • 相关阅读:
    css3系列之@font-face
    css3系列之text-shadow 浮雕效果,镂空效果,荧光效果,遮罩效果
    css3系列之linear-gradient() repeating-linear-gradient() 和 radial-gradient() repeating-radial-gradient()
    css3系列之详解background
    css3系列之详解border-image
    css3系列之详解box-shadow
    css3系列之详解border-radius
    promise与async和await的区别
    JavaScript 里的闭包是什么?应用场景有哪些?
    ES6箭头函数中的this绑定问题
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/12418981.html
Copyright © 2011-2022 走看看