• 洛谷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

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

  • 相关阅读:
    8-12接口测试进阶-1数据驱动
    8-5接口测试用例设计与编写-4
    用CentOS 7打造合适的科研环境
    linux版本qq的安装
    CentOS下安装福昕PDF软件
    CENTOS install summary
    centos6 install mplayer(multimedia)
    基于u盘安装centos6.0
    CentOS6.3挂载读写NTFS分区
    制作win7+ubuntu +winPE+CDlinux多系统启动U盘
  • 原文地址:https://www.cnblogs.com/chloris/p/11753510.html
Copyright © 2020-2023  润新知