noip2017d2t2

看数据范围想到状压,我们知道最后是选出一颗生成树,但边权的计算有一些有趣;

我们先选一个点做根;然后就发现边的权和深度有关;那我们按深度dp;即按层dp;

dp[i][s]表示前i层选的点集为s,转移时我们枚举s的补集的子集ss;对于ss中的每个点,

我们连上他到s中点的最小边;但这样连的边没办法保证深度为i+1,但我们就强制把他设成i+1;

这样感觉很错,但其实是对的,因为这样只会使答案变大,而且还可以保证真正的答案被枚举到;

相当于是一种对限制的扩大。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdio>
using namespace std;
typedef long long ll;
const ll inf=1e16;
ll map[20][20],dis[15][(1<<12)+10];
ll dp[15][(1<<12)+10],z;
int n,m,x,y;
int main(){
    cin>>n>>m;
    if(!m){puts("0");return 0;}
    for(int i=0;i<=12;++i)
      for(int j=0;j<=12;++j)map[i][j]=inf;
    for(int i=0;i<=12;++i)
      for(int j=0;j<=(1<<12);++j)dis[i][j]=inf;
    for(int i=0;i<=12;++i)
      for(int j=0;j<=(1<<12);++j)dp[i][j]=inf;
    for(int i=1;i<=m;++i){
        scanf("%d%d%d",&x,&y,&z);
        map[x][y]=map[y][x]=min(map[x][y],z);
    }
    int sta=(1<<n)-1;
    for(int s=1;s<=sta;++s){
        for(int i=1;i<=n;++i){
            if((1<<(i-1))&s)continue;
            for(int j=1;j<=n;++j){
                if((1<<(j-1))&s)dis[i][s]=min(dis[i][s],map[i][j]);
            }
        }
    }
    for(int i=1;i<=n;++i)dp[1][1<<(i-1)]=0;
    for(int s=1;s<=sta;++s){
        int st=sta-s;
        for(int ss=st;ss;ss=(ss-1)&st){
            ll res=0;
            for(int i=1;i<=n;++i){
                if((1<<(i-1))&ss)res+=dis[i][s];
            }
            for(ll i=1;i<n;++i){
                dp[i+1][ss|s]=min(dp[i+1][ss|s],dp[i][s]+res*i);
            }
        }
    }
    ll ans=inf;
    for(int i=2;i<=n;++i)ans=min(ans,dp[i][sta]);
    cout<<ans;
    return 0;
}
原文地址:https://www.cnblogs.com/dibaotianxing/p/7922911.html