bzoj2595 [Wc2008]游览计划(最小斯坦纳树(状压DP))

83 篇文章 0 订阅
51 篇文章 1 订阅

bzoj2595 [Wc2008]游览计划

原题地址http://www.lydsy.com/JudgeOnline/problem.php?id=2595

题意:
这里写图片描述
这里写图片描述

数据范围
N,M,K≤10,其中K为景点的数目。输入的所有整数均在[0,2^16]的范围内

题解:

斯坦纳树推荐看这篇

dp[i][j][s]表景点联通状态为s,以(i,j)为根的最小代价。
两种转移:
dp[i][j][s]=dp[i][j][s’]+dp[i][j][s^s’]-a[i][j](合并子集,实际上是让树分叉)
dp[i][j][s]=dp[u][v][s’]+a[i][j] (向相邻点转移,因为转移有环,跑spfa)

一层一层地来就好。

感觉和Noip2017Day2T2挺像。

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N=15;
const int inf=0x3f3f3f3f;
struct node
{
    int x,y,s;
    node(int x,int y,int s):x(x),y(y),s(s){}
};
queue<node> Q;
int n,m,po[N],a[N][N],cnt=0,dp[N][N][1500],top,pre[N][N][1500][3],fx[4]={0,-1,0,1},fy[4]={1,0,-1,0},num[N][N];
bool vis[N][N];
void dfs(int x,int y,int s)
{
    vis[x][y]=1; if(!s) return;
    dfs(pre[x][y][s][0],pre[x][y][s][1],pre[x][y][s][2]);
    if(x==pre[x][y][s][0]&&y==pre[x][y][s][1]) dfs(pre[x][y][s][0],pre[x][y][s][1],s^pre[x][y][s][2]);
}
void cal(int x,int y)
{
    printf("%d\n",dp[x][y][top-1]);
    dfs(x,y,top-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("_");
        }
        printf("\n");
    }
}
int main()
{
    for(int i=0;i<=10;i++) po[i]=1<<i;
    memset(dp,0x3f,sizeof(dp));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {
        scanf("%d",&a[i][j]);
        if(!a[i][j]) {dp[i][j][po[cnt]]=0; num[i][j]=cnt; cnt++;}
    }
    top=1<<cnt;
    for(int s=1;s<top;s++)
    {
        memset(vis,0,sizeof(vis));
        for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            for(int ss=(s-1)&s;ss;ss=(ss-1)&s)
            {
                int kk=s^ss;
                if(dp[i][j][ss]+dp[i][j][kk]-a[i][j]<dp[i][j][s])
                {
                    dp[i][j][s]=dp[i][j][ss]+dp[i][j][kk]-a[i][j];
                    pre[i][j][s][0]=i; pre[i][j][s][1]=j; pre[i][j][s][2]=ss;

                }
            }
            if(dp[i][j][s]<inf) {Q.push(node(i,j,s)); vis[i][j]=1;}
        }
        while(!Q.empty())
        {
            node u=Q.front(); Q.pop(); 
            int x=u.x; int y=u.y; int s=u.s; vis[x][y]=0;
            for(int d=0;d<4;d++)
            {
                int u=x+fx[d]; int v=y+fy[d];
                if(u>0&&u<=n&&v>0&&v<=m)
                {
                    int ss=a[u][v]?0:po[num[u][v]]; int ns=ss|s;
                    if(dp[x][y][s]+a[u][v]<dp[u][v][ns])
                    {
                        dp[u][v][ns]=dp[x][y][s]+a[u][v];
                        pre[u][v][ns][0]=x; pre[u][v][ns][1]=y; pre[u][v][ns][2]=s;
                        if(ns==s&&!vis[u][v]) {vis[u][v]=1; Q.push(node(u,v,s));}
                    }
                }
            }
        }
    }
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    if(a[i][j]==0) {cal(i,j); return 0;}
    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值