斯坦纳树入门 UVALive-5717, HDU-3311, POJ-3123, HYSBZ-2595

斯坦纳树是求在树上使得一部分点集联通的子树,最小斯坦纳树就是在此前提下最小化子树的权值之和.
通常就是在动态规划中以二进制表示联通状态进行求解,由于状态转移方程的形式跟spfa求解的方式相似度很高,所以可以利用spfa进行状态转移.

感觉题目的模型还是比较容易看出来的,有些题目进行了简单的变形,如求斯坦纳树森林,或者联通点分为两类,对两类点的数量一定的要求.

A - Peach Blossom Spring HDU - 4085

k个房子k个庇护所,每一座房子都必须可以和一个庇护所联通.
由于不是所有要求点都需要联通,所以题目就是要求斯坦纳森林,但是由于上述限制条件,合法的状态必须是房子数量和庇护所数量相同.
最终树合并成森林时对状态判断一下合法性就行了.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef unsigned long long ull;
#define fi first
#define se second
#define mp make_pair
#define pb push_back
const int maxn=65;
const int maxm=1<<10;
const int maxe=2015;
const int inf=0x3f3f3f3f;
const int seed=1e9+7;
int n,m,k,nn;
struct edge{int to,nxt,len;}e[maxe];
int head[maxn],tot;
void adde(int u,int v,int l){e[tot].to=v;e[tot].len=l;e[tot].nxt=head[u];head[u]=tot++;}

int d[maxn][maxm];
int dp[maxm];
int s[maxn];

void init(){
    tot=0;
    memset(head,-1,sizeof(head));
    memset(s,0,sizeof(s));
    scanf("%d%d%d",&n,&m,&k);
    nn=1<<(2*k);
    for(int i=1;i<=n;i++){
        for(int j=0;j<nn;j++)d[i][j]=inf;
    }
    int a,b,c;
    for(int i=0;i<m;i++){
        scanf("%d%d%d",&a,&b,&c);
        adde(a,b,c);adde(b,a,c);
    }
    for(int i=1;i<=k;i++){
        s[i]=1<<(i-1);d[i][s[i]]=0;
        s[n-i+1]=1<<(k+i-1);d[n-i+1][s[n-i+1]]=0;
    }
}

queue<pii> que;
bool inque[maxn][maxm];

inline bool check(int msk){
    int v1=0,v2=0;
    for(int i=0;i<k;i++)if(msk>>i&1)v1++;
    for(int i=k;i<2*k;i++)if(msk>>i&1)v2++;
    return v1==v2;
}

inline bool update(int x,int y,int w){
    if(w<d[x][y]){d[x][y]=w;return 1;}
    return 0;
}

void spfa(){
    while(!que.empty()){
        int x=que.front().fi,y=que.front().se;que.pop();
        for(int i=head[x];i!=-1;i=e[i].nxt){
            int v=e[i].to,w=e[i].len;
            if(update(v,y|s[v],d[x][y]+w)&&y==(y|s[v])&&!inque[v][y]){
                inque[v][y]=1;que.push(mp(v,y));
            }
        }
        inque[x][y]=0;
    }
}

void work(){
    for(int msk=0;msk<nn;msk++){
        for(int x=1;x<=n;x++){
            for(int i=(msk-1)&msk;i;i=(i-1)&msk){
                d[x][msk]=min(d[x][msk],d[x][i|s[x]]+d[x][(msk-i)|s[x]]);
            }
            if(d[x][msk]<inf){que.push(mp(x,msk));inque[x][msk]=1;}
        }
        spfa();
    }
}

int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        init();
        work();
        for(int j=1;j<nn;j++){
            dp[j]=inf;
            for(int i=1;i<=n;i++)dp[j]=min(dp[j],d[i][j]);
        }
        for(int i=1;i<nn;i++){
            if(check(i)){
                for(int j=(i-1)&i;j;j=(j-1)&i){
                    if(check(j)){
                        dp[i]=min(dp[i],dp[j]+dp[i-j]);
                    }
                }
            }
        }
        if(dp[nn-1]>=inf)printf("No solution\n");
        else printf("%d\n",dp[nn-1]);
    }
    return 0;
}

B - Dig The Wells HDU - 3311

要求使得所有要求点都有水,有水的方法可以是在这个点上挖井,也可以是通过道路从其他点引过来.
不需要全部联通,只需要每个斯坦纳树内的都有水即可.
由于可以在任意一个点上挖井,所有初始化dp的时候特殊处理以下即可.
可以在二进制状态上多加一位表示联通块内有没有挖井就可以了.
最终树合并森林的时候要求两个子状态都是有挖井.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef unsigned long long ull;
#define fi first
#define se second
#define mp make_pair
#define pb push_back
const int maxn=1100+15;
const int maxm=1<<6;
const int maxe=1e4+15;
const int inf=0x3f3f3f3f;
const int seed=1e9+7;
int n,m,p,nn;
struct edge{
    int to,nxt,len;
}e[maxe];
int tot;
int head[maxn];

void adde(int u,int v,int l){
    e[tot].to=v;e[tot].len=l;e[tot].nxt=head[u];head[u]=tot++;
}

int s[maxn];
int dp[maxn][maxm];
int a,b,c;
void init(){
    nn=1<<(n+1);
    for(int i=1;i<=n+m;i++){
        for(int j=0;j<nn;j++){
            dp[i][j]=inf;
        }
        s[i]=0;
    }
    for(int i=1;i<=n;i++){
        scanf("%d",&a);
        dp[i][1<<i]=0;
        dp[i][(1<<i)+1]=a;
        s[i]=(1<<i);
    }
    for(int i=1;i<=m;i++){
        scanf("%d",&a);
        dp[i+n][0]=0;
        dp[i+n][1]=a;
    }
    for(int i=0;i<p;i++){
        scanf("%d%d%d",&a,&b,&c);
        adde(a,b,c);
        adde(b,a,c);
    }
}

queue<pii> que;
bool inque[maxn][maxm];

void spfa(){
    while(!que.empty()){
        pii cnt=que.front();que.pop();
        int u=cnt.fi;
        int sta=cnt.se;
        for(int i=head[u];i!=-1;i=e[i].nxt){
            int v=e[i].to;
            int l=e[i].len;
            if(dp[u][sta]+l<dp[v][sta|s[v]]){
                dp[v][sta|s[v]]=dp[u][sta]+l;
                if((sta|s[v])==sta&&!inque[v][sta]){
                    inque[v][sta]=1;
                    que.push(mp(v,sta));
                }
            }
        }
        inque[u][sta]=0;
    }
}

void stantree(){
    for(int j=0;j<nn;j++){
        for(int i=1;i<=n+m;i++){
            for(int k=j&(j-1);k;k=j&(k-1)){
                dp[i][j]=min(dp[i][j],dp[i][k|s[i]]+dp[i][j-k|s[i]]);
            }
            if(dp[i][j]<inf){
                que.push(mp(i,j));inque[i][j]=1;
            }
        }
        spfa();
    }
}

int ansdp[maxn];
int main(){
    while(~scanf("%d%d%d",&n,&m,&p)){
        tot=0;memset(head,-1,sizeof(head));
        init();
        stantree();
        for(int j=0;j<nn;j++){
            ansdp[j]=inf;
            for(int i=1;i<=n+m;i++){
                ansdp[j]=min(ansdp[j],dp[i][j]);
            }
        }
        for(int j=0;j<nn;j++){
            if(j&1)
            for(int k=j&(j-1);k;k=(k-1)&j){
                if(k&1){
                    int cc=j-k|1;
                    ansdp[j]=min(ansdp[j],ansdp[k]+ansdp[cc]);
                }
            }
        }
        printf("%d\n",ansdp[nn-1]);
    }
    return 0;
}

C - Ticket to Ride POJ - 3123

给出树并且要让树上的四个点对联通
本质上没什么差别,还是斯坦纳森林.
只不过合并森林是合法判断要改变以下就可以了.

#include<cstdio>
#include<map>
#include<algorithm>
#include<cstring>
#include<queue>
#include<string>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef unsigned long long ull;
#define fi first
#define se second
#define mp make_pair
#define pb push_back
const int maxn=3000+15;
const int maxm=1<<8;
const int maxe=2000+15;
const int inf=0x3f3f3f3f;
const int seed=1e9+7;
int n,m,p,nn;
struct edge{
    int to,nxt,len;
}e[maxe];
int tot;
int head[maxn];

void adde(int u,int v,int l){
    e[tot].to=v;e[tot].len=l;e[tot].nxt=head[u];head[u]=tot++;
}

int s[maxn];
int dp[maxn][maxm];
int a,b,c,cc;
int ansdp[maxn];
//char str[50];
char str[50];
map<string,int> ma;
void init(){
    nn=1<<8;
    for(int i=1;i<=n;i++){
        for(int j=0;j<nn;j++){
            dp[i][j]=inf;
        }
        s[i]=0;
    }
    for(int i=0;i<m;i++){
        scanf("%s",str);
        int id1=ma[str];
        scanf("%s",str);
        int id2=ma[str];
        scanf("%d",&c);
        adde(id1,id2,c);
        adde(id2,id1,c);
    }
    for(int i=0;i<4;i++){
        scanf("%s",str);
        int id1=ma[str];
        s[id1]|=1<<i;
        scanf("%s",str);
        int id2=ma[str];
        s[id2]|=1<<(7-i);
        dp[id1][s[id1]]=0;
        dp[id2][s[id2]]=0;
    }
}

queue<pii> que;
bool inque[maxn][maxm];

void spfa(){
    while(!que.empty()){
        pii cnt=que.front();que.pop();
        int u=cnt.fi;
        int sta=cnt.se;
        for(int i=head[u];i!=-1;i=e[i].nxt){
            int v=e[i].to;
            int l=e[i].len;
            if(dp[u][sta]+l<dp[v][sta|s[v]]){
                dp[v][sta|s[v]]=dp[u][sta]+l;
                if((sta|s[v])==sta&&!inque[v][sta]){
                    inque[v][sta]=1;
                    que.push(mp(v,sta));
                }
            }
        }
        inque[u][sta]=0;
    }
}

void stantree(){
    for(int j=0;j<nn;j++){
        for(int i=1;i<=n;i++){
            for(int k=j&(j-1);k;k=j&(k-1)){
                dp[i][j]=min(dp[i][j],dp[i][k|s[i]]+dp[i][j-k|s[i]]);
            }
            if(dp[i][j]<inf){
                que.push(mp(i,j));inque[i][j]=1;
            }
        }
        spfa();
    }
}


bool check(int sta){
    for(int i=0;i<4;i++){
        if(sta>>i&1){
            if(sta>>(7-i)&1)continue;
            return 0;
        }
        else{
            if(sta>>(7-i)&1)return 0;
        }
    }
    return 1;
}

int main(){
    while(~scanf("%d%d",&n,&m),n+m){
        tot=0;memset(head,-1,sizeof(head));
        ma.clear();
        for(int i=1;i<=n;i++){
            scanf("%s",str);
            ma[str]=i;
        }
        init();
        stantree();
        for(int j=0;j<nn;j++){
            ansdp[j]=inf;
            for(int i=1;i<=n;i++){
                ansdp[j]=min(ansdp[j],dp[i][j]);
            }
        }
        for(int j=0;j<nn;j++){
            if(check(j)){
                for(int k=j&(j-1);k;k=(k-1)&j){
                    if(check(k)){
                        ansdp[j]=min(ansdp[j],ansdp[k]+ansdp[j-k]);
                    }
                }
            }
        }
        printf("%d\n",ansdp[nn-1]);
    }
    return 0;
}

E - 游览计划 HYSBZ - 2595

经典题目.不是树了,变成了四联通的二维矩阵.
由于权值不再是边了,而变成点,所以合并的时候要考虑是不是会重复计算一个点的点权两次.
记录路径也是要特殊处理一下.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef unsigned long long ull;
#define fi first
#define se second
#define mp make_pair
#define pb push_back
const int maxn=10;
const int maxm=1<<10;
const int maxe=2015;
const int inf=0x3f3f3f3f;
const int seed=1e9+7;
int n,m,k,nn;

struct node{
    int x,y,sta;
    node(){}
    node(int a,int b,int c){
        x=a;y=b;sta=c;
    }
};

int ma[maxn][maxn];
int s[maxn][maxn];
int dp[maxn][maxn][maxm];
bool inque[maxn][maxn][maxm];
pair<node,node> g[maxn][maxn][maxm];
char ansmap[maxn][maxn];

void init(){
    int tot=0;
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            if(ma[i][j]==0){s[i][j]=(1<<tot);tot++;}
            else s[i][j]=0;
            ansmap[i][j]='_';
            if(ma[i][j]==0)ansmap[i][j]='x';
        }
    }
    nn=1<<tot;
    for(int i=0;i<n;i++)
    for(int j=0;j<m;j++){
        for(int k=0;k<nn;k++)dp[i][j][k]=inf;
        if(s[i][j]){
            dp[i][j][s[i][j]]=0;
            g[i][j][s[i][j]].fi=node(-1,-1,-1);
            g[i][j][s[i][j]].se=node(-1,-1,-1);
        }
    }
}

int dx[4]={0,0,1,-1};
int dy[4]={-1,1,0,0};
queue<node> que;
void spfa(){
    while(!que.empty()){
        node cnt=que.front();que.pop();
        int x=cnt.x,y=cnt.y,sta=cnt.sta;
        for(int i=0;i<4;i++){
            int nx=x+dx[i],ny=y+dy[i];
            if(nx<0||nx>=n||ny<0||ny>=m)continue;
            int newsta=sta|s[nx][ny];
            if(dp[x][y][sta]+ma[nx][ny]<dp[nx][ny][newsta]){
                dp[nx][ny][newsta]=dp[x][y][sta]+ma[nx][ny];
                g[nx][ny][newsta].fi=node(x,y,sta);
                g[nx][ny][newsta].se=node(-1,-1,-1);
                if(newsta==sta&&!inque[nx][ny][newsta]){
                    inque[nx][ny][newsta]=1;
                    que.push(node(nx,ny,newsta));
                }
            }
        }
        inque[x][y][sta]=0;
    }
}

void work(){
    for(int j=1;j<nn;j++){
        for(int x=0;x<n;x++){
            for(int y=0;y<m;y++){
                for(int i=(j-1)&j;i;i=(i-1)&j){
                    if(dp[x][y][i]+dp[x][y][j-i]-ma[x][y]<=dp[x][y][j]){
                        dp[x][y][j]=dp[x][y][i]+dp[x][y][j-i]-ma[x][y];
                        g[x][y][j].fi=node(x,y,i);
                        g[x][y][j].se=node(x,y,j-i);
                    }
                }
                if(dp[x][y][j]<inf){que.push(node(x,y,j));inque[x][y][j]=1;}
            }
        }
        spfa();
    }
}


void dfs(int x,int y,int sta){
    if(ma[x][y])ansmap[x][y]='o';
    node lef=g[x][y][sta].fi;
    node rig=g[x][y][sta].se;
    if(lef.x!=-1)dfs(lef.x,lef.y,lef.sta);
    if(rig.x!=-1)dfs(rig.x,rig.y,rig.sta);
}

int main(){
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            scanf("%d",&ma[i][j]);
        }
    }
    init();
    work();
    int ans=inf;
    int ansx,ansy;
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            if(dp[i][j][nn-1]<ans){
                ans=dp[i][j][nn-1];
                ansx=i;ansy=j;
            }
        }
    }
    printf("%d\n",ans);
    dfs(ansx,ansy,nn-1);
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            putchar(ansmap[i][j]);
        }
        printf("\n");
    }
    return 0;
}

/*
10 10
0 0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
*/
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值