• 最小斯坦纳树


    最小斯坦纳树

    问题描述:

    给定一个包含 (n) 个结点和 (m) 条带权边的无向连通图 (G=(V,E))

    再给定包含 (k) 个结点的点集 (S),选出 (G) 的子图 (G'=(V',E')) 使得:

    1. (Ssubseteq V')

    2. (G′) 为连通图;

    3. (E′) 中所有边的权值和最小。

    你只需要求出 (E′)中所有边的权值和。

    分析:

    (dp[i][s]) 表示 以 i 为根的一棵树,包含集合 S 中所有点的最小代价 (i) 号点不一定在 (s)

    1. (w[i][j] + dp[j][s] -> dp[i][s])

    2. (dp[i][T] + dp[i][S-T] -> dp[i][S]quad(T subseteq S))

    第二类转移可以用枚举子集来转移,第一类转移是一个三角不等式,可以用(spfa)或者(dijkstra)

    第一类转移复杂度为(O(mlog m imes 2^k))

    第二类转移复杂度为(O(n imes 3^n)) ,子集枚举的时间复杂度可以用二项式定理求出

    代码:

    const int N = 100 + 5;
    const int M = 1010;
    int n, m, k, x, y, z, tot;
    int head[N], ver[M], nxt[M], edge[M], dp[N][4200];
    int p[N], vis[N];
    priority_queue<pair<int,int>> q;
    void add(int x, int y, int z){
        ver[++tot] = y; edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
    }
    void dijkstra(int s){
        memset(vis, 0, sizeof vis);
        while(q.size()){
            auto t = q.top();q.pop();
            int x = t.second;
            if(vis[x]) continue;
            vis[x] = 1;
            for(int i=head[x];i;i=nxt[i]){
                int y = ver[i];
                if(dp[y][s] > dp[x][s] + edge[i]){
                    dp[y][s] = dp[x][s] + edge[i];
                    q.push(make_pair(-dp[y][s], y));
                }
            }
        }
    }
    int main(){
        scanf("%d%d%d", &n, &m, &k);
        for(int i=1;i<=m;i++){
            scanf("%d%d%d", &x, &y, &z);
            add(x, y, z);add(y, x, z);
        }
        memset(dp, 0x3f, sizeof dp);
        for(int i=1;i<=k;i++){
            scanf("%d", &p[i]);
            dp[p[i]][1<<(i-1)] = 0;
        }
        for(int s = 1; s < (1 << k); s++){
            for(int i=1; i <= n; i++){
                for(int subs = (s-1)&s; subs; subs = s & (subs-1)){
                    dp[i][s] = min(dp[i][s], dp[i][subs]+dp[i][s^subs]);
                }
                if(dp[i][s]!=inf) q.push(make_pair(-dp[i][s], i));
            }
            dijkstra(s);
        }
        printf("%d
    ", dp[p[1]][(1<<k)-1]);
    
        return 0;
    }
    

    例题 2019南昌邀请赛A

    给出一张图,求让 (4) 对点相互可以到达的最小边权值。仅要求一对之间,一对与另外一对可到达也可不到达。

    题目链接

    先对于8个点求出最小斯坦纳树,然后求出(res[s] = min_{iin [1,n]} dp[i][s]) ,表示点集为 (s) 的最小斯坦纳树路径和。有些 (s) 是不合法的,因为它不能完整的包含4对点中的某一对(如果包含某个点,则必须包含该点的配对点,否则就不包含)。去除这些不合法的集合 (s), 在合法的集合之间进行状压(dp)转移即可。

    const int N = 30 + 5;
    const int M = 2000;
    int n, m, k, tot, cnt;
    int head[N], ver[M], nxt[M], edge[M], dp[N][(1<<8)], res[1<<8];
    int p[N], vis[N];
    map<string,int> mp;
    priority_queue<pair<int,int>> q;
    void add(int x, int y, int z){
        ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
    }
    int getId(string str){
        if(mp.count(str))return mp[str];
        return mp[str] = ++cnt;
    }
    void dijkstra(int s){
        memset(vis, 0, sizeof vis);
        while(q.size()){
            auto t = q.top();q.pop();
            int x = t.second;
            if(vis[x]) continue;
            vis[x] = 1;
            for(int i=head[x];i;i=nxt[i]){
                int y = ver[i];
                if(dp[y][s] > dp[x][s] + edge[i]){
                    dp[y][s] = dp[x][s] + edge[i];
                    q.push(make_pair(-dp[y][s], y));
                }
            }
        }
    }
    bool check(int s){
        for(int i=0;i<8;i+=2){
            if((s >> i & 1) != (s >> (i+1) & 1)) return false;
        }
        return true;
    }
    int main(){
        scanf("%d%d", &n, &m);
        for(int i=1;i<=n;i++){
            string str;cin >> str;
            getId(str);
        }
        for(int i=1;i<=m;i++){
            int x, y, z;
            string s, t;
            cin >> s >> t >> z;
            x = getId(s);
            y = getId(t);
            add(x, y, z);add(y, x, z);
        }
        for(int i=1;i<=4;i++){
            string s, t;
            cin >> s >> t;
            p[i*2-1] = getId(s);
            p[i*2] = getId(t);
        }
        k = 8;
        memset(dp, 0x3f, sizeof dp);
        for(int i=1;i<=k;i++){
            dp[p[i]][1<<(i-1)] = 0;
        }
        for(int S = 1; S < (1 << k); S++){
            for(int i=1; i <= n; i++){
                for(int subs = (S-1)&S; subs; subs = S & (subs-1)){
                    dp[i][S] = min(dp[i][S], dp[i][subs]+dp[i][S^subs]);
                }
                if(dp[i][S]!=inf) q.push(make_pair(-dp[i][S], i));
            }
            dijkstra(S);
        }
        //以上为斯坦纳树模板
        memset(res, 0x3f, sizeof res);
        for(int s = 1; s < (1<<k);s++){
            if(!check(s)) continue; // 不合法 s 直接跳过
            for(int i=1;i<=n;i++){
                res[s] = min(res[s], dp[i][s]); //更新得到res[s]
            }
        }
        for(int s = 1; s < (1 << k); s++){
            for(int subs=(s-1)&s;subs;subs=(subs-1)&s){
                res[s] = min(res[s], res[subs] + res[s^subs]); //不合法的s保证不会发生转移,因为提前赋值为inf
            }
        }
        printf("%d
    ", res[(1<<k)-1]);
        return 0;
    }
    
  • 相关阅读:
    HTTP 状态码
    Buffer 流文件
    事件(Event)机制 .on() .emit() .once() .removeListener()
    UDP node客户端和服务端
    node全局变量  node定时器 系统自带的模块 http服务器
    标题省略,不会自动换行
    SpringBoot 出现 Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported
    循环对象
    npm
    根据对象的key值,查找对应的属性value
  • 原文地址:https://www.cnblogs.com/1625--H/p/12580187.html
Copyright © 2020-2023  润新知