luogu3959【noip2017】宝藏(状压dp)

首先处理出w[s1][s2]表示把s2的点都连到s1上的最小花费(s2中的每个点只能连到s1中的某个点上)。怎么处理呢?先 O(2nn) 做出单点到一个集合的花费,然后 O(3nn) 的处理出其他的集合到集合的花费。然后就可以dp了。
dp[i][S][s1]表示深度最大为i,已连接了S的点,最深层的点为s1的最小花费,我们枚举一个S的补集的子集s2来转移即可。
dp[i][S][s1]+w[s1][s2]>dp[i+1][S|s2][s2]
复杂度 O(n4n) ,还要注意空间,要滚动数组。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define N 4100
inline char gc(){
    static char buf[1<<16],*S,*T;
    if(S==T){T=(S=buf)+fread(buf,1,1<<16,stdin);if(T==S) return EOF;}
    return *S++;
}
inline int read(){
    int x=0,f=1;char ch=gc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=gc();}
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=gc();
    return x*f;
}
int n,m,dp[2][N][N],bin[15],mp[15][15],ans=inf,w[N][N];//dp[i][S][s1],深度最大为i,已连接了S的点,最深层的点为s1,
int main(){
//  freopen("a.in","r",stdin);
    n=read();m=read();bin[0]=1;memset(mp,inf,sizeof(mp));
    for(int i=1;i<=m;++i){
        int x=read()-1,y=read()-1;mp[x][y]=mp[y][x]=min(mp[y][x],read());
    }for(int i=1;i<=n;++i) bin[i]=bin[i-1]<<1;memset(w,inf,sizeof(w));
    for(int s1=1;s1<bin[n]-1;++s1){
        for(int i=0;i<n;++i){
            if(s1&bin[i]) continue;
            for(int j=0;j<n;++j){
                if((s1&bin[j])==0||mp[i][j]==inf) continue;
                w[s1][bin[i]]=min(w[s1][bin[i]],mp[i][j]);
            }
        }
    }for(int s1=1;s1<bin[n]-1;++s1){
        int T=bin[n]-1^s1;
        for(int s2=T;s2;s2=(s2-1)&T){
            int tot=0;
            for(int i=0;i<n;++i) if(s2&bin[i]) ++tot;
            if(tot==1) continue;w[s1][s2]=0;
            for(int i=0;i<n;++i){
                if((s2&bin[i])==0) continue;
                if(w[s1][bin[i]]==inf){w[s1][s2]=inf;break;}
                w[s1][s2]+=w[s1][bin[i]];
            }
        }
    }memset(dp,inf,sizeof(dp));
    for(int i=0;i<n;++i) dp[0][bin[i]][bin[i]]=0;int now=0;
    if(n==1) ans=0;
    for(int i=1;i<=n;++i){
        memset(dp[now^1],inf,sizeof(dp[0]));
        for(int S=1;S<bin[n]-1;++S){
            int T=bin[n]-1^S;
            for(int s1=S;s1;s1=(s1-1)&S){
                if(dp[now][S][s1]==inf) continue;
                for(int s2=T;s2;s2=(s2-1)&T){
                    if(w[s1][s2]==inf) continue;
                    dp[now^1][S|s2][s2]=min(dp[now^1][S|s2][s2],dp[now][S][s1]+i*w[s1][s2]);
                }
            }
        }now^=1;
        for(int i=1;i<=bin[n]-1;++i) ans=min(ans,dp[now][bin[n]-1][i]);
    }printf("%d\n",ans);
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值