• 【知识点】斯坦纳树


    简介:

    就是个状压dp起了个高端的名字。

    例题:

    给定一张图,其中有p个关键点,连接每个点有一个代价,要求用最少的代价连通所有关键点。

    $nleq 100,pleq 10$。

    做法:

    发现最小生成树并不能做,虚树也不能做,实际上这个问题就没有多项式复杂度的做法。

    于是考虑状压dp。设$dp(i,j)$表示当前位于点i(不一定是关键点),与关键点的连通状态为j的最小代价。

    转移有两种,一种是合并当前点的两个状态,一种是同样的连通状态从一个点转移到另一个点。

    具体地:

    • $dp(i,j)=min { dp(i,j),dp(i,k)+dp(i,j-k)-val_i }$,表示合并点i的两个状态。
    • $dp(i,j)=min { dp(i,j),dp(k,j)+val_i }$,表示点k的状态转移到点i。

    由于每个状态对应的连通块肯定是一棵树,所以位于点i就相当于以i为根,两种转移也相当于合并子树和添加新根的操作。

    第二种转移可以用spfa优化,总复杂度$O(n imes 3^{p})$。

    代码([WC2008]游览计划):

    #include<bits/stdc++.h>
    #define maxn 10
    #define maxm 505
    #define inf 0x7fffffff
    #define ll long long
    #define pii pair<int,int>
    #define mp make_pair
    #define fi first
    #define se second
    #define rint register int
    #define debug(x) cerr<<#x<<": "<<x<<endl
    #define fgx cerr<<"--------------"<<endl
    #define dgx cerr<<"=============="<<endl
    
    using namespace std;
    int n,m,A[maxm],dp[maxm][1<<maxn],vis[maxm];
    int hd[maxm],nxt[maxm],cst[maxm],cnt;
    int inq[maxm],to[maxm],rk[maxm];
    pii fr[maxm][1<<maxn];
    
    inline int read(){
        int x=0,f=1; char c=getchar();
        for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        return x*f;
    }
    
    inline int id(int x,int y){return (x-1)*m+y;}
    inline void addedge(int u,int v,int w){to[++cnt]=v,cst[cnt]=w,nxt[cnt]=hd[u],hd[u]=cnt;}
    
    inline void spfa(int s){
        memset(inq,0,sizeof(inq));
        queue<int> q;
        for(int i=1;i<=n*m;i++)
            if(dp[i][s]<=1e9)
                q.push(i),inq[i]=1;
        while(!q.empty()){
            int u=q.front(); q.pop(),inq[u]=0;
            for(int i=hd[u];i;i=nxt[i]){
                int v=to[i],w=cst[i];
                if(dp[v][s]>dp[u][s]+w){
                    dp[v][s]=dp[u][s]+w,fr[v][s]=mp(u,s);
                    if(!inq[v]) q.push(v),inq[v]=1;
                }
            }
        }
    }
    
    inline void dfs(int i,int j){
        vis[i]=1; if(!fr[i][j].fi) return; 
        dfs(fr[i][j].fi,fr[i][j].se);
        if(fr[i][j].fi==i) dfs(fr[i][j].fi,j^fr[i][j].se);
    }
    
    int main(){
        n=read(),m=read();
        memset(dp,63,sizeof(dp));
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++){
                int w=read(); A[id(i,j)]=w;
                if(j>1) addedge(id(i,j-1),id(i,j),w);
                if(i>1) addedge(id(i-1,j),id(i,j),w);
                if(j<m) addedge(id(i,j+1),id(i,j),w);
                if(i<n) addedge(id(i+1,j),id(i,j),w);
                if(w==0) rk[++rk[0]]=id(i,j),dp[id(i,j)][1<<(rk[0]-1)]=0;
            }
        for(int j=0;j<(1<<rk[0]);j++){
            for(int i=1;i<=n*m;i++)
                for(int k=j;k;k=(k-1)&j)
                    if(dp[i][j]>dp[i][j^k]+dp[i][k]-A[i])
                        dp[i][j]=dp[i][j^k]+dp[i][k]-A[i],fr[i][j]=mp(i,k);
            spfa(j);
        }
        int ans=inf,res=0;
        for(int i=1;i<=n*m;i++)
            if(dp[i][(1<<rk[0])-1]<ans)
                ans=dp[i][(1<<rk[0])-1],res=i;
        printf("%d
    ",ans);
        dfs(res,(1<<rk[0])-1);
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                if(A[id(i,j)]==0) printf("x");
                else if(vis[id(i,j)]) printf("o");
                else printf("_");
            }
            printf("
    ");
        }
        return 0;
    }
    斯坦纳树
  • 相关阅读:
    狗狗对主人的十句话
    微软提供正确卸载IE7的方法并恢复IE6
    c语言操作符的优先级
    linux 常用命令每日更新
    Visual Studio快捷键大全
    Opera将尽快发布补丁修复桌面浏览器漏洞 狼人:
    KILL杀毒软件重出江湖 公司股权全内资组成 狼人:
    图文:2010中国计算机网络安全年会华为展台 狼人:
    微软再次警告IE安全漏洞成为攻击目标 狼人:
    微软发布三月安全公告 两个补丁修补严重漏洞 狼人:
  • 原文地址:https://www.cnblogs.com/YSFAC/p/13214023.html
Copyright © 2020-2023  润新知