[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)
原文地址:https://www.cnblogs.com/alecli/p/9935372.html