洛谷P3959 宝藏

写一道题码一篇题解系列,足以证明我有多菜

因为写每道题都有没注意的或者刚学会的东西【欲哭无泪】

题面:https://www.luogu.org/problem/P3959

这里的做法是状压DP。

把原图分成一层一层的点集,假设下一层可以由上一层拓展到。设dp[i][j]为在第i层转移到j状态的代价,那么转移到dp[i][j]的贡献就是dp[i-1][k]+num*(i-1),其中k是j的子集,num为k中的点扩展出j中有但k中没有的点的最小代价。

转移的时候要确保k能拓展到j,这个可以预处理g[k][j]。同时还要预处理每个状态i拓展到某个点j的最小代价dis[j][i],用来计算num。

初始化是把每个单独点在第一层的代价设为0,表示它作为地面。答案是在每一层转移到全集的最小值。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,m;
int maxn;
long long e[1<<12],dp[13][1<<12],ans,dis[13][1<<12],num,d[13][13],g[1<<12][1<<12];
void work(){
    for(int i=1;i<=maxn;i++){
        for(int j=2;j<=n;j++){
            for(int k=(i-1)&i;k;k=(k-1)&i){
                if(g[k][i]){
                    num=0;
                    for(int l=1;l<=n;l++){
                        if((i&(1<<(l-1)))&&!(k&(1<<(l-1)))&&dis[l][k]<=500000){
                            num+=dis[l][k];
                        }
                    }
                    dp[j][i]=min(dp[j][i],dp[j-1][k]+(j-1)*num);
                }
            }
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    maxn=(1<<n);
    for(int i=1,x,y;i<=m;i++){
        long long z;
        scanf("%d%d%lld",&x,&y,&z);
        if(x==y)continue;
        if(!d[x][y])d[x][y]=d[y][x]=z;
        else d[x][y]=d[y][x]=min(d[x][y],z);
    } 
    memset(dis,0x3f3f3f3f,sizeof(dis));
    for(int i=0;i<maxn;i++){
        for(int j=1;j<=n;j++){
            for(int k=1;k<=n;k++){
                if((i&(1<<(k-1)))&&!(i&(1<<(j-1)))){
                    if(d[k][j]){
                        dis[j][i]=min(dis[j][i],d[k][j]);
                    }
                }
            }
        }
    }
    int flag,flag1;
    for(int i=1;i<maxn;i++){
        for(int j=(i-1)&i;j;j=(j-1)&i){
            flag=0;
            for(int k=1;k<=n;k++){
                flag1=0;
                if((i&(1<<(k-1)))&&!(j&(1<<(k-1)))){
                    for(int l=1;l<=n;l++){
                        if(j&(1<<(l-1))){
                            if(d[l][k]){
                                flag1=1;
                                break;
                            }
                        }
                    }
                    if(!flag1){
                        flag=1;
                        break;
                    }
                }
            }
            if(!flag)g[j][i]=1;
        }
    }
    ans=0x3f3f3f3f;
    memset(dp,0x3f3f3f3f,sizeof(dp));
    for(int i=1;i<=n;i++){
        dp[1][(1<<(i-1))]=0;
    }
    work();
    for(int j=1;j<=n;j++)ans=min(ans,dp[j][maxn-1]);
    printf("%lld
",ans);
    return 0;
}
View Code

复杂度是O(3n*n2)。

一开始我是处理点集对应的边来判断转移是否合法,显然会出锅…就算边集是包含关系也不一定能转移,中间可能有断层。然后空间卡着开…最后还有个坑就是重边取min,其实这个根据数据范围应该就能看出来的orz

我太菜了,这题写了一年:(还是不太会做状压DP

洛谷题解里还有模拟退火以及搜索+剪枝的做法。

原文地址:https://www.cnblogs.com/chloris/p/11753510.html