bzoj 4774: 修路

Time Limit: 10 Sec  Memory Limit: 256 MB
Submit: 282  Solved: 132
[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
 
好像是个斯坦纳树的模板题。。。
虽然到现在也不知道斯坦纳树具体是个什么玩意,,,大概就是能处理有后效性的图上状压dp ???
因为关键点很少,所以可以状压dp。
设f(S,i)为目前选的边的构成的树的根是i,且关键点集合状态是S的最优值。
转移有两种:
1. f(S,i)=min{f(s,i)+f(S^s,i)},其中s是S的子集。
相当于合并两颗树(如果有重复边也不要紧,因为有重复边的话最后一定不会在最优答案中)
2.f(S,i)=min{f(S,x)+val(x,i)},其中x与i有边相连。
相当于将树根扩展出去一个节点。
 
最后再根据f数组进行子集dp,只能从满足条件的子集转移(即有i号节点必须有n-i+1号节点)
 
/**************************************************************
    Problem: 4774
    User: JYYHH
    Language: C++
    Result: Accepted
    Time:2180 ms
    Memory:14904 kb
****************************************************************/
 
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;
int f[300][10005],n,m,hd[10005],d;
int to[20005],val[20005],ne[20005];
int dis[10005],ans[300],num,ci[20],all;
bool iq[10005];
int q[400005],l,r;
int main(){
    memset(f,0x3f,sizeof(f));
    memset(ans,0x3f,sizeof(ans));
    ci[0]=1;
    for(int i=1;i<=10;i++) ci[i]=ci[i-1]<<1;
     
    scanf("%d%d%d",&n,&m,&d);
    int uu,vv,ww;
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&uu,&vv,&ww);
        to[++num]=vv,ne[num]=hd[uu],hd[uu]=num,val[num]=ww;
        to[++num]=uu,ne[num]=hd[vv],hd[vv]=num,val[num]=ww;     
    }
     
    for(int i=1;i<=d;i++) f[ci[i-1]][i]=0;
    for(int i=n-d+1;i<=n;i++) f[ci[n+d-i]][i]=0;
    memset(f[0],0,sizeof(f[0]));
     
    all=ci[d<<1]-1;
    for(int S=1;S<=all;S++){
        for(int i=1;i<=n;i++)
            for(int s=(S-1)&S;s;s=(s-1)&S) f[S][i]=min(f[S][i],f[s][i]+f[s^S][i]);
         
        for(int i=1;i<=n;i++) dis[i]=f[S][i],q[i]=i,iq[i]=1;
         
        l=1,r=n;
        while(l<=r){
            int x=q[l++];
            for(int i=hd[x];i;i=ne[i]) if(dis[x]+val[i]<dis[to[i]]){
                dis[to[i]]=dis[x]+val[i];
                if(!iq[to[i]]) q[++r]=to[i],iq[to[i]]=1;
            }
            iq[x]=0;
        }
         
        for(int i=1;i<=n;i++) f[S][i]=dis[i];
    }
     
    for(int s=0;s<=all;s++){
        bool fl=0;
        for(int i=1;i<=d;i++) if(((s&ci[i-1])?1:0)+((s&ci[i+d-1])?1:0)==1){
            fl=1;
            break;
        }
        if(fl) continue;
         
        for(int i=1;i<=n;i++) ans[s]=min(ans[s],f[s][i]);
    }
     
    for(int S=1;S<=all;S++)
        for(int s=(S-1)&S;s;s=(s-1)&S) ans[S]=min(ans[S],ans[s]+ans[S^s]);
     
    if(ans[all]!=ans[all+1]) printf("%d\n",ans[all]);
    else puts("-1");
    return 0;
}

 

 
 
posted @ 2018-01-17 14:49  蒟蒻JHY  阅读(358)  评论(0编辑  收藏  举报