洛谷 P3959/NOIP2017 提高组 宝藏(状压dp,生成树)

传送门
题目大意:给一个带权无向图,求生成树的最小代价.代价 = (起点深度dep[i]-1)*边权edge[i].
n的范围只有12,很容易想到用状压来解决这道题.考虑怎么确定状态.代价只和深度和边权有关.我们可以把最大深度作为dp的阶段,各个点的打通情况作为附加状态.1表示已经打通,0表示尚未打通.再来考虑状态之间如何转移.
假设当前的状态为j.它如果可以从k状态转移过来的话需要满足两个条件.

  1. k必须是j的子集,这个判断可以用位运算计算.k&j==k.这个应该很好理解.不作解释.
  2. j必须是k能拓展得到的状态,设k中打通的点能拓展到的状态pre[k],那么同样的j必须是pre[k]的子集.

满足了这两点之后j和k之间就可以进行转移了.为了优化dp的效率,我们还需要预处理出一个road[i][j]数组记录i状态打通到j所需要的最小代价.有了这个数组后我们继续预处理出一个dis[i][j]数组表示i状态到达j状态所需要的最小代价.
这里有一个问题就是说如何判断哪一个打通的节点是深度为i-1的节点,但其实是可以忽略掉这个问题的,因为如果该节点不是深度为i-1的节点,那么它就不会是更优的答案,这是做题常见的一种思考方式,弱化一下限制能更加方便的编写程序.

还有一个值得注意的点是枚举j的子集的时候只需要枚举到j-1即可.

代码

#pragma GCC optimize(2)
#define LL long long
#define pq priority_queue
#define ULL unsigned long long
#define pb push_back
#define mem(a,x) memset(a,x,sizeof a)
#define pii pair<int,int>
#define fir(i,a,b) for(int i=a;i<=(int)b;++i)
#define afir(i,a,b) for(int i=(int)a;i>=b;--i)
#define ft first
#define vi vector<int>
#define sd second
#define ALL(a) a.begin(),a.end()
#define bug puts("-------")
#define mpr(a,b) make_pair(a,b)
#include <bits/stdc++.h>

using namespace std;
const int N = 13;
const int M = 1 << 12;
const int INF = 0x3f3f3f3f;

inline void read(int &a){
    
    
    int x = 0,f=1;char ch = getchar();
    while(ch<'0'||ch>'9'){
    
    if(ch=='-')f=-1;ch=getchar();}
    while(ch<='9'&&ch>='0'){
    
    x=x*10+ch-'0';ch=getchar();}
    a = x*f;
}
int n,m,tot,w[N][N],pre[M],dp[N][M],road[M][N];
vi sel[M],dis[M];

void prework(){
    
    
    fir(i,0,tot){
    
    
        pre[i] = i;
        vi tmp;
        fir(j,0,n-1)
            tmp.pb(i>>j&1);
        fir(j,0,n-1){
    
    
            if(!tmp[j]) continue;
            road[i][j] = 0;
            fir(k,0,n-1){
    
    
                if(w[j][k] == INF) continue;
                pre[i] |= (1 << k);
                road[i][k] = min(w[j][k],road[i][k]);
            }
        }
    }
    fir(j,0,tot){
    
    
        fir(k,0,j-1){
    
    
            if((j&k) != k || (j&pre[k]) != j) continue;
            sel[j].pb(k);
            int sum = 0;
            fir(i,0,n-1){
    
    
                if(( ( (j^k)>>i ) & 1) == 0) continue;
                sum += road[k][i];
            }
            dis[j].pb(sum);
        }
    }
}

int main(){
    
    
    cin >> n >> m;
    tot = (1<<n)-1;
    mem(w,INF);
    mem(dp,INF);
    mem(road,INF);
    fir(i,1,m){
    
    
        int x,y,z;
        cin >> x >> y >> z;
        x--;y--;
        w[x][y] = min(w[x][y],z);
        w[y][x] = min(w[y][x],z);
    }
    prework();
    fir(i,0,n-1)
        dp[1][1<<i] = 0;
    fir(i,2,n){
    
    
        fir(j,0,tot){
    
    
            fir(k,0,(int)sel[j].size()-1){
    
    
                dp[i][j] = min(dp[i][j],dp[i-1][sel[j][k]]+(i-1)*dis[j][k]);
            }
        }
    }
    int ans = INF;
    fir(i,1,n) ans = min(ans,dp[i][tot]);
    cout << ans;
    
    return 0;
}  

猜你喜欢

转载自blog.csdn.net/weixin_45590210/article/details/108099363