【BZOJ4006】【JLOI2015】管道连接

Description

  
  传送门
  
  
  
  
  

Solution

  
  题目要求相同颜色的点必须在一个连通块中,但会有多个颜色同属一个连通块使得解更优的情况。
  
  想一想DP能否行得通:设\(g_i\)表示已考虑颜色状态为\(i\)时,最小合法方案的代价。
  
  首先,\(g_i\)可以有一个直观的初值:由颜色属于\(i\)的点构建的一棵最小生成树的边权和。(初始化)
  
​  接下来,如何考虑两部分颜色各自的连通块合起来作为最优解的情况?(两子集合并更新)
  
​  更新\(g_i\)时,我们枚举\(i\)的两个不相交子集\(s_1,s_2\)\(s_1|s_2=i\),并用\(g_{s_1}+g_{s_2}\)来更新\(g_i\),即直接尝试将两个连通块取交。不需要担心两部分会有重复计算某一条边的情况,因为这是子集枚举DP,有边重复计算的转移自然不会成为最优转移,最优的转移一定会被枚举到(或者就是初值最优)。
  
​  以上两种方法并用,就可以顺利DP出\(g\)
  
​  答案就是\(g_{all}\)\(all\)是包含所有颜色的集合。
  
​  对于\(g\)的初值,直接用斯坦纳树计算所有关键点的不同组合的最小生成树即可。
  
  
  

Code

  

#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
const int N=1005,M=3005,C=10,INF=1e9;
int n,m,p,col[N],tin[N][2],colst[C+1];
int idcnt,id[N],rep[C+1];
int clis[C+1],ccnt;
int f[N][1<<C],g[1<<C];
queue<int> q;
bool inq[N];
int h[N],tot;
struct Edge{int v,w,next;}e[M*2];
inline int bit(int i){return !i?0:(1<<(i-1));}
inline bool in(int st,int i){return (st>>(i-1))&1;}
inline void addEdge(int u,int v,int w){
    e[++tot]=(Edge){v,w,h[u]}; h[u]=tot;
    e[++tot]=(Edge){u,w,h[v]}; h[v]=tot;
}
void read(){
    scanf("%d%d%d",&n,&m,&p);
    for(int i=1,u,v,w;i<=m;i++){
        scanf("%d%d%d",&u,&v,&w);
        addEdge(u,v,w);
    }
    for(int i=1,c,u;i<=p;i++){
        scanf("%d%d",&c,&u);// c ?
        tin[i][0]=c; tin[i][1]=u;
        clis[++ccnt]=c;
    }
    sort(clis+1,clis+1+ccnt);
    ccnt=unique(clis+1,clis+1+ccnt)-clis-1;
    for(int i=1,u,c;i<=p;i++){
        c=lower_bound(clis+1,clis+1+ccnt,tin[i][0])-clis;
        u=tin[i][1];
        col[u]=c;
        id[u]=++idcnt;          
        rep[c]=u;
        colst[c]|=bit(id[u]);
    }
}
void spfa(int st){
    while(!q.empty()){
        int u=q.front(); q.pop();
        inq[u]=false;
        for(int i=h[u],v;i;i=e[i].next){
            v=e[i].v;
            int ns=st|bit(id[v]);
            if(f[u][st]+e[i].w<f[v][ns]){
                f[v][ns]=f[u][st]+e[i].w;
                if(!inq[v]){
                    inq[v]=true;
                    q.push(v);
                }
            }
        }
    }
}
void steinerTree(){
    int all=1<<p;
    for(int i=1;i<=n;i++){
        for(int j=0;j<all;j++) f[i][j]=INF;
        f[i][bit(id[i])]=0;
    }
    for(int j=1;j<all;j++){
        for(int i=1;i<=n;i++)
            if(!(id[i]&&!in(j,id[i]))){
                for(int sub=(j-1)&j;sub;sub=(sub-1)&j){
                    int x=sub|bit(id[i]),y=(j^sub)|bit(id[i]);
                    if(f[i][x]+f[i][y]<f[i][j])
                        f[i][j]=f[i][x]+f[i][y];
                }
                if(f[i][j]!=INF)
                    q.push(i),inq[i]=true;
        }
        spfa(j);
    }
}
void solve(){
    int all=1<<ccnt;
    for(int i=1;i<all;i++){
        int st=0,rt=0;
        for(int j=1;j<=ccnt;j++)
            if(in(i,j))
                st|=colst[j],rt=rep[j];
        g[i]=f[rt][st];
        for(int sub=(i-1)&i;sub;sub=(sub-1)&i)
            g[i]=min(g[i],g[sub]+g[i^sub]);
    }
    printf("%d\n",g[all-1]);
}
int main(){
    read();
    steinerTree();
    solve();
    return 0;
}

转载于:https://www.cnblogs.com/RogerDTZ/p/9248671.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值