zoukankan      html  css  js  c++  java
  • [NOIp2017]宝藏

    题意

    Here

    思考

    终于在NOIp2018前一天写完了这一道状压。

    怎么说呢,这一题实现起来不是太难,思路也不算是太难,如果想到状压,想到转移方程的话基本上就可做了。

    最小生成树的错误在于它没有考虑到深度对答案的影响,本题另外一个模拟退火的解法也不能算是真正意义上的正解(蒟蒻认为状压还是此题的最好解法,别喷我 (QWQ)

    考虑如何设计状态:

    1. 当前点集我们肯定要记录
    2. 如果我们记录当前在哪个点,这似乎不太好转移,再仔细看答案与 (k) 有关,即当前点离根还有多少个点,这不就是深度吗,我们可以按深度,一层一层地转移

    我们用 (f[i][S]) 表示当前深度为 (i), 当前点集为 (S) 的最小花费,转移方程:

    [f[i][S] = min_{S_1subseteq S}{f[i-1][S_1] + (i-1)*cost_{S_1 o S}} ]

    其中, (cost) 表示从状态 (S_1) 转移到 (S) 的最短距离,这个我们先预处理出来,于是我们就可以愉快 (A) 掉它了,(ps:) 复杂度不是很会算

    代码

    #include<bits/stdc++.h>
    using namespace std;
    int read(){
        int x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
        return x * f;
    }
    const int N = 13;
    const int oo = 0x3f3f3f3f;
    int n, m, S;
    int f[N][1<<12], dis[N][N], MINd[1<<12][1<<12];
    int main(){
        n = read(), m = read(); S = (1 << n) - 1;
        memset(dis, 0x3f, sizeof(dis));
        memset(f, 0x3f, sizeof(f));
        for(int i=1; i<=m; i++){
            int u = read(), v = read(), d = read();
            dis[u][v] = dis[v][u] = min(dis[u][v], d);
        }
        for(int i=0; i<=S; i++){//枚举状态
            for(int j=i; j; j=(j-1)&i){//枚举子集
                int tmp = i ^ j; //需要计算的点
                for(int k=0; k<=n-1; k++){
                    if(tmp & (1 << k)){//此时计算k+1这个点
                        int nowd = oo;
                        for(int p=0; p<=n-1; p++){//枚举起点
                            if(j & (1 << p)){
                                nowd = min(nowd, dis[p+1][k+1]);
                            }
                        }
                        if(nowd == oo) {
                            MINd[j][i] = nowd;
                            continue;
                        }
                        MINd[j][i] += nowd;
                    }
                }
            }
        }//以上是对子集与子集间最小路程的预处理
        for(int i=0; i<=n-1; i++){
            f[1][1<<i] = 0;
        }
        for(int i=2; i<=n; i++){
            for(int j=0; j<=S; j++){//枚举状态
                for(int k=j; k; k=(k-1)&j){
                    if(MINd[k][j] < oo){
                        f[i][j] = min(f[i][j], f[i-1][k] + (i-1)*MINd[k][j]);
                    }
                }
            }
        }
        int ans = oo;
        for(int i=1; i<=n; i++) ans = min(ans, f[i][S]);
        cout << ans;
        return 0;
    }
    
    
    

    总结

    要设计出易于转移的状态,例如这一题,转移时的中间量是与深度有关的,所以状态中存了一维代表深度。

    还有一个小 (trick) :在枚举一个点集的子集时,如果暴力枚举肯定 (GG),我们可以这样枚举(这里枚举的是非空子集,可以自行模拟一遍)

        for(int i=S; i; i=(i-1)&S)
    
  • 相关阅读:
    Linux安装软件时90%的人会遇到这个报错,如何解决?
    mongo下查询ObjectId(id)类型的数据
    解决nohup启动logstash生成的文件过大的问题
    ES集群服务器下线节点扩容后上线的流程
    knife4j的使用记录
    mongodb的update函数更新数据,更新文档中的某个具体字段的数据
    Python celery异步框架
    VueX插件使用
    Vue-router插件使用
    Vue 自定义指令
  • 原文地址:https://www.cnblogs.com/alecli/p/9935372.html
Copyright © 2011-2022 走看看