bzoj 2595: [Wc2008]游览计划(斯坦纳树)

2595: [Wc2008]游览计划

Time Limit: 10 Sec   Memory Limit: 256 MBSec   Special Judge
Submit: 1253   Solved: 574
[ 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个节点的最小生成树(貌似也可以生成森林)。

dp[i][j] 表示到达i这个点,当前状态为j ,注意第二维是状压,用来表示给定点是否被选。

枚举子树的形态:dp[ i ][ j ]=min{ dp[ i ][ j ],dp[ i ][ k ]+dp[ i ][ l ] },其中k和l是对j的一个划分。
按照边进行松弛:dp[ i ][ j ]=min{ dp[ i ][ j ],dp[ i' ][ j ]+w[ i ][ i' ] },其中i和i'之间有边相连,利用spfa 进行松弛。

总复杂度为O(n*3^k)

进行spfa的时候只需要对当前层的节点进行spfa就行了,不需要整个图完全松弛一遍,因为更高的层都可以通过枚举子集而变成若干个更低的层,这样一次spfa的复杂度一下就降了下来,变成了O(n)级别

http://endlesscount.blog.163.com/blog/static/821197872012525113427573/  这里有更详细的介绍。

#include<iostream>  
#include<cstdio>  
#include<cstring>  
#include<algorithm>  
#include<cmath>  
#include<queue>  
#define N 13  
#define LL long long  
#define pa pair<int,int>  
#define inf 1000000000  
using namespace std;  
int n,m,a[N][N],vis[N][N],mi[N];  
int f[N][N][(1<<N)],k1,can[N][N];  
struct data{  
    int x,y,thd;  
}pre[N][N][(1<<N)];  
int x[10]={0,0,-1,1},y[10]={-1,1,0,0};  
queue<pa> q;  
void spfa(int sta)  
{  
    while (!q.empty())  
    {  
        int nowx=q.front().first; int nowy=q.front().second;  
        q.pop();  
        for (int i=0;i<4;i++)  
        {  
            int xx=nowx+x[i]; int yy=nowy+y[i];  
            if (xx<=0||yy<=0||xx>n||yy>m) continue;  
            if (f[xx][yy][sta]>f[nowx][nowy][sta]+a[xx][yy])  
             {  
                f[xx][yy][sta]=f[nowx][nowy][sta]+a[xx][yy];  
                pre[xx][yy][sta]=(data){nowx,nowy,sta};  
                if (!can[xx][yy]){  
                    can[xx][yy]=1;  
                    q.push(make_pair(xx,yy));  
                }  
             }  
        }  
        can[nowx][nowy]=0;  
    } 
}   
void dfs(int x,int y,int sta)  
{  
    if (x==inf||pre[x][y][sta].thd==0) return;  
    vis[x][y]=1;  
    data t=pre[x][y][sta];  
    dfs(t.x,t.y,t.thd);  
    if (t.x==x&&t.y==y)  dfs(x,y,sta-t.thd);  
}  
int main()  
{  
    scanf("%d%d",&n,&m);  
    mi[0]=1;  
    for (int i=1;i<=11;i++) mi[i]=mi[i-1]*2;  
    for (int i=1;i<=n;i++)  
     for (int j=1;j<=m;j++)  
      for (int k=0;k<mi[11];k++)    
        f[i][j][k]=pre[i][j][k].x=inf;  
    for (int i=1;i<=n;i++)  
     for (int j=1;j<=m;j++)  
     {  
        scanf("%d",&a[i][j]);  
        if (!a[i][j]) f[i][j][mi[k1]]=0,k1++;  
     }  
    for (int sta=1;sta<mi[k1];sta++)  
    {  
        for (int i=1;i<=n;i++)  
         for (int j=1;j<=m;j++)  
          {  
            for (int s=sta&(sta-1);s;s=sta&(s-1))  
            {  
                int t=f[i][j][s]+f[i][j][sta-s]-a[i][j]; 
                if (t<f[i][j][sta])  
                {  
                    f[i][j][sta]=t;  
                    pre[i][j][sta]=(data){i,j,s};  
                }  
            }  
            if (f[i][j][sta]<inf)  
              q.push(make_pair(i,j)),can[i][j]=1;
          }  
        spfa(sta);  
    }  
    int x,y;  
    for (int i=1;i<=n;i++)  
    {  
        bool pd=false;  
        for (int j=1;j<=m;j++)  
        if (!a[i][j]){  
            x=i; y=j; pd=true; break;  
        }  
        if (pd)  break;  
    }  
    printf("%d\n",f[x][y][mi[k1]-1]);  
    dfs(x,y,mi[k1]-1);  
    for (int i=1;i<=n;i++)  
     for (int j=1;j<=m;j++)  
      {  
        if (!a[i][j]) printf("x");  
        else if (vis[i][j]) printf("o");  
        else printf("_");  
        if (j==m) printf("\n");  
      }  
}  


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值