bzoj4774 修路

4774: 修路

Time Limit: 10 Sec  Memory Limit: 256 MB
Submit: 290  Solved: 137
[Submit][Status][Discuss]

Description

村子间的小路年久失修,为了保障村子之间的往来,法珞决定带领大家修路。对于边带权的无向图 G = (V, E),
请选择一些边,使得1 <= i <= d, i号节点和 n - i + 1 号节点可以通过选中的边连通,最小化选中的所有边
的权值和。
 

 

Input

第一行两个整数 n, m,表示图的点数和边数。接下来的 m行,每行三个整数 ui, vi, wi,表示有一条 ui 与 vi 
之间,权值为 wi 的无向边。
1 <= d <= 4
2d <= n <= 10^4
0 <= m <= 10^4
1 <= ui, vi <= n
1 <= wi <= 1000

 

Output

一行一个整数,表示答案,如果无解输出-1

 

Sample Input

10 20 1
6 5 1
6 9 4
9 4 2
9 4 10
6 1 2
2 3 6
7 6 10
5 7 1
9 7 2
5 9 10
1 6 8
4 7 4
5 7 1
2 6 9
10 10 6
8 7 2
10 9 10
1 2 4
10 1 8
9 9 7

Sample Output

8
分析:斯坦纳树模板题.维护数组f[i][j]表示从i出发,使得状态为j的最小花费,这里的状态是用二进制表示的:01010101之类的,如果第i位为1,表示第i号点已经被连通.再维护一个数组g[i],表示状态为j的最小花费,这里就不管是从哪个点出发了.g[j] = min{f[i][j]}.
         最后更新g[i]用i的子集更新.若i的子集j满足条件(如果点p连通了,那么点p+d也连通),并且j^i也满足条件,就更新g[i].为什么要判断是否满足条件才能更新呢?例子:
,上下之间的点都是要配对的,如果直接合并了,则可能不配对.

 

最后输出答案g[1 << (2 * d) - 1].
#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int maxn = 20010,inf = 0x7ffffff;

int n,m,d,head[maxn],to[maxn],nextt[maxn],w[maxn],tot = 1,f[maxn][1 << 9],g[1 << 9],maxx,vis[maxn],dis[maxn];
queue <int> q;

void add(int x,int y,int z)
{
    w[tot] = z;
    to[tot] = y;
    nextt[tot] = head[x];
    head[x] = tot++;
}

void spfa(int sta)
{
    memset(vis,0,sizeof(vis));
    queue <int> q;
    for (int i = 1; i <= n; i++)
    {
        q.push(i);
        vis[i] = 1;
    }
    while (!q.empty())
    {
        int u = q.front();
        q.pop();
        vis[u] = 0;
        for (int i = head[u];i;i = nextt[i])
        {
            int v = to[i];
            if (f[v][sta] > f[u][sta] + w[i])
            {
                f[v][sta] = f[u][sta] + w[i];
                if (!vis[v])
                {
                    vis[v] = 1;
                    q.push(v);
                }
            }
        }
    }
}

bool check(int sta)
{
    for (int i = 0; i < d; i++)
    {
        int temp = 1 << i;
        if (sta & temp)
        {
            int temp2 = 1 << (i + d);
            if (!(sta & temp2))
                return false;
        }
        temp = 1 << (i + d);
        if (sta & temp)
        {
            int temp2 = 1 << i;
            if (!(sta & temp2))
                return false;
        }
    }
    return true;
}

int main()
{
    scanf("%d%d%d",&n,&m,&d);
    for (int i = 1; i <= m; i++)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }
    maxx = (1 << (2 * d));
    for (int i = 1; i <= n; i++)
        for (int j = 0; j < maxx; j++)
            f[i][j] = inf,g[j] = inf;
    for (int i = 1; i <= d; i++)
    {
        f[i][1 << (i - 1)] = 0;
        f[n - i + 1][1 << (d + i - 1)] = 0;
    }

    for (int j = 0; j < maxx; j++)
    {
        for (int i = 1; i <= n; i++)
        {
            for (int k = j; k; k = (k - 1) & j)
                f[i][j] = min(f[i][j],f[i][k] + f[i][j ^ k]);
        }
        spfa(j);
        for (int i = 1; i <= n; i++)
            g[j] = min(g[j],f[i][j]);
    }
    for (int i = 0; i < maxx; i++)
        for (int j = i; j; j = (j - 1) & i)
            if (check(j) && check(i ^ j))
                g[i] = min(g[i],g[j] + g[j ^ i]);
    if (g[maxx - 1] < inf)
        printf("%d\n",g[maxx - 1]);
    else
        puts("-1");

    return 0;
}

 

posted @ 2018-01-17 20:26  zbtrs  阅读(232)  评论(0编辑  收藏  举报