P4294 [WC2008]游览计划

传送门

斯坦纳树

给一个联通图,求 $k$ 个关键点联通的最小生成树权值

设 $f[o][i]$ 表示当前关键点选择状态为 $o$ ,以点 $i$ 为根的树的最小权值

初始 $f[1<<(i-1)][i]=val[i]$ ,$val[i]$ 表示点 $i$ 的权值

那么从小到大枚举状态 $o$

对于每一个状态枚举 $o$ 的真子集 $op$,

则 $f[o][i]=min(f[o][i],f[o-op][i]+f[op][i]-val[i])$ 注意代价要减去 $val[i]$ ,因为两个状态合并时点 $i$ 的代价会算两次

这样转移还不够,还要考虑一个树自己扩展出去

所以枚举与根 $i$ 相连的点 $v$

则 $f[o][v]=min(f[o][v],f[o][i]+val[v])$ ,这样dp的顺序不好确定,但是发现这个很像 SPFA 的式子,所以用 SPFA 来进行转移

总结一下,对于每个状态,先考虑树的合并,再考虑树的扩展

至于为什么这样做是对的呢:

感性理解一下,这样显然会考虑到所有的情况,所以是对的2333....

SPFA时以 $f[o][i]!=INF$ 为起点

因为此题要输出路径,所以维护一个 $fa[o][i]$ 存状态 $o,i$ 是从哪两个子树合并的,对于扩展的子树就特殊处理一下

骚操作:枚举一个状态的真子集 : $for(int op=(o-1)&o;op;op=(op-1)&o)$

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
using namespace std;
typedef long long ll;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=27,M=2027,INF=1e9+7;
int fir[M],from[M<<3],to[M<<3],cntt;
void add(int &a,int &b)
{
    from[++cntt]=fir[a]; fir[a]=cntt;
    to[cntt]=b;
}
int n,m,K,tot;
int val[M],pos[N],id[N][N];
int f[M][M];
bool inq[M],mp[N][N];
struct path {
    int o1,x1,o2,x2;
}fa[M][M];
void SPFA(int p)
{
    queue <int> q;
    for(int i=1;i<=tot;i++) if(f[p][i]<INF) q.push(i),inq[i]=1;
    while(!q.empty())
    {
        int x=q.front(); q.pop();  inq[x]=0;
        for(int i=fir[x];i;i=from[i])
        {
            int &v=to[i];
            if(f[p][v]>f[p][x]+val[v])
            {
                f[p][v]=f[p][x]+val[v];
                fa[p][v]=(path){p,x,-1,v};//扩展的子树特殊处理成-1
                if(!inq[v]) q.push(v),inq[v]=1;
            }
        }
    }
}
void dfs(int o,int x)
{
    int o1=fa[o][x].o1,x1=fa[o][x].x1,o2=fa[o][x].o2,x2=fa[o][x].x2;
    if(!(o1|x1|o2|x2)) return;
    dfs(o1,x1);
    if(o2==-1) mp[(x2-1)/m+1][(x2-1)%m+1]=1;//如果是-1则说明此点有志愿者
    else dfs(o2,x2);//否则向下一个子树转移
}
int main()
{
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    memset(f,0x3f,sizeof(f));
    n=read(),m=read(); tot=n*m;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            id[i][j]=(i-1)*m+j;//把点缩起来
            val[id[i][j]]=read();
            if(!val[id[i][j]]) pos[++K]=id[i][j];
            if(j>1) add(id[i][j-1],id[i][j]),add(id[i][j],id[i][j-1]);
            if(i>1) add(id[i-1][j],id[i][j]),add(id[i][j],id[i-1][j]);
        }
    int mx=(1<<K)-1;
    for(int i=1;i<=K;i++) f[1<<i-1][pos[i]]=0;
    SPFA(0);
    for(int o=1;o<=mx;o++)
    {
        for(int j=1;j<=tot;j++)
            for(int op=(o-1)&o;op;op=(op-1)&o)
                if(f[o][j]>f[o^op][j]+f[op][j]-val[j])
                {
                    f[o][j]=f[o^op][j]+f[op][j]-val[j];
                    fa[o][j]=(path){o-op,j,op,j};
                }
        SPFA(o);
    }
    int ans=INF,rt=0;
    for(int i=1;i<=tot;i++) if(f[mx][i]<ans) ans=f[mx][i],rt=i;
    dfs(mx,rt);
    printf("%d\n",ans);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if(!val[id[i][j]]) printf("x");
            else if(mp[i][j]) printf("o");
            else printf("_");
        }
        printf("\n");
    }
    return 0;
}

 

posted @ 2019-01-31 08:28  LLTYYC  阅读(229)  评论(0编辑  收藏  举报