• bzoj2595 / P4294 [WC2008]游览计划


    P4294 [WC2008]游览计划

    斯坦纳树

    斯坦纳树,是一种神奇的树。它支持在一个连通图上求包含若干个选定点的最小生成树

    前置算法:spfa+状压dp+dfs(大雾)


    我们设$f[o][P]$为第$o$个点上状态为$P$的最小代价,其中状态使用二进制存储已经连接了多少个选定点。

    初始化:显然对于每个选定点,$f[o][1<<k]=0$,$k$为该选定点在所有选定点中的编号。其他为$inf$


    蓝后就是将状态从小到大枚举进行递推

    $for(P=0;P<(1<<k);++P)$

    对于每层递推,枚举所有点$1$~$o$;

    我们先考虑这个点连接了2个不同连通块(链)的状况(并为spfa做好准备

    于是我们枚举$P$的真子集进行递推

    $for(j=(P-1)&P;j;j=(j-1)&P)$

    枚举真子集↑

    $f[o][P]=min(f[o][P],f[o][j]+f[o][P$^$j]-val[o]);$

    注意该式适用于计算点权,减去$val[o]$是去掉重复点权。如果计算边权需作修改。


    但是这样显然远远不够。于是我们用$spfa$通过类似$dp$的形式处理好剩下的所有状况。

    对于前面的$f[o][P]$,任何$f[o][P]<inf$都应作为每层spfa的起点(显然spfa也是每层执行)


    在共$K$个给定点中,随意找一个给定点作为树根$rt$。

    $ans=f[rt][(1<<K)-1]$


    对于本题$(extra)$:输出一种方案

    我们在每次更新$f[o][P]$时

    用$fa[o][P]$记下$f[o][P]$从哪个状态推导而来

    最后从树根$rt$用$dfs$倒推回去,更新答案即可。

    void dfs(pii u,int o){//u:坐标
        par G=fa[F(u)][o];
        if(!G.se) return;
        use[u.fi][u.se]=1;
        if(G.fi==u) dfs(u,o^G.se);//两个联通块合并而来,则要从两条路递推回去。
        dfs(G.fi,G.se);
    }

    $code:$

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    using namespace std;
    typedef pair<int,int> pii;
    typedef pair<pii,int> par;
    #define mp make_pair
    #define fi first
    #define se second
    int d1[4]={0,1,0,-1};
    int d2[4]={1,0,-1,0};
    int n,m,tot,k,inf,f[105][1030],id[15][15],a[15][15];
    bool in[15][15],use[15][15];
    par fa[105][1030]; pii rt;
    queue <pii> h;
    inline int F(pii x){return id[x.fi][x.se];}
    void spfa(int o){
        while(!h.empty()){
            pii x=h.front(); h.pop(); in[x.fi][x.se]=0;
            for(int i=0;i<4;++i){
                int r1=x.fi+d1[i],r2=x.se+d2[i],to=id[r1][r2];
                if(r1<0||r1>=n||r2<0||r2>=m) continue;
                if(f[to][o]>f[F(x)][o]+a[r1][r2]){
                    f[to][o]=f[F(x)][o]+a[r1][r2];
                    fa[to][o]=mp(x,o);
                    if(!in[r1][r2]) h.push(mp(r1,r2));
                }
            }
        }
    }
    void dfs(pii u,int o){//逆推找可行方案
        par G=fa[F(u)][o];
        if(!G.se) return;
        use[u.fi][u.se]=1;//选择点u
        if(G.fi==u) dfs(u,o^G.se);
        dfs(G.fi,G.se);
    }
    int main(){
        memset(f,63,sizeof(f)); inf=f[0][0];
        scanf("%d%d",&n,&m);
        for(int i=0;i<n;++i)
            for(int j=0;j<m;++j){
                scanf("%d",&a[i][j]);
                id[i][j]=++tot;
                if(!a[i][j]) rt=mp(i,j),f[tot][1<<k]=0,++k;
            }
        for(int P=0;P<(1<<k);spfa(P),++P)//每层递推的最后来一次spfa
            for(int x=0;x<n;++x)
                for(int y=0;y<m;++y){
                    int o=id[x][y];
                    for(int j=(P-1)&P;j;j=(j-1)&P)//枚举真子集
                        if(f[o][P]>f[o][j]+f[o][P^j]-a[x][y]){
                            f[o][P]=f[o][j]+f[o][P^j]-a[x][y];
                            fa[o][P]=mp(mp(x,y),j);
                        }
                    if(f[o][P]<inf) h.push(mp(x,y)),in[x][y]=1;//可行点都能spfa
                }
        printf("%d
    ",f[F(rt)][(1<<k)-1]); dfs(rt,(1<<k)-1);
        for(int i=0;i<n;++i,printf("
    "))
            for(int j=0;j<m;++j){
                if(!a[i][j]) printf("x");
                else if(use[i][j]) printf("o");
                else printf("_");
            }
        return 0;
    }
  • 相关阅读:
    梦和现实, 哪个更真实呢? 程序的现实? 还是上古的梦?
    C#基础知识总结(一)
    C#学习路线
    C# 成员默认访问权限(public、private、protected、internal)
    C#基础知识总结(二)
    正则表达式C#正则表达式的符号及例子
    Dynamics CRM 2016/365 窗体中添加按钮
    01 tsung安装
    07 tsung 参数化
    02 jmeter 简单发送http请求
  • 原文地址:https://www.cnblogs.com/kafuuchino/p/10336659.html
Copyright © 2020-2023  润新知