《算法竞赛进阶指南》0x56状态压缩DP AcWing529 宝藏


	《算法竞赛进阶指南》0x56状态压缩DP AcWing529 宝藏
[编程语言教程]

题目链接:https://www.acwing.com/problem/content/531/

 

题目给出不超过12个点,和一些边,第一个点不用花费,其余的点都要根据深度和扩展的边长来确定花费,通过dp,将层数作为阶段,每个阶段用状态压缩记录12个点中已经走过的点,转移的过程是从j状态转移到k,这里两个状态的转移一定要合法,在深度i-1转移到i时,对每个扩展的点到j集合的距离之和乘上(i-1).

这里有一点十分重要,从j转移到k如何保证一定是从深度i-1的点扩展的呢?

证明:

①、假设全部是从深度为i-1的点转移的,那么由于i-1层的状态都是最优的,加上新扩展的点到集合的距离也是最短的,无疑转移的结果是最优的。

②、假设原状态是S,加入一些新的点之后,新状态是S1,扩展的过程中包括一些点,这些点是从小于i-1层的状态扩展过来的,那么一定存在一个集合,包括S1中除了由i-1层扩展过来的结点以外的所有结点,这个状态在前i-1层中一定是最优的,把这个集合记为S2,那么显然从S2扩展到S1就是①中的情况。最优结果不会遗漏。

证毕。

代码:

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
const int N = 15,M=1<<12;
int f[N][M];
int expand[M],road[M][N];
vector<int> valid[M],cost[M];//每个状态的下一个合法状态以及状态扩展的最小代价和 
int a[N][N]; 
int n,m;
void pre(){
    memset(road,0x3f,sizeof road);
    for(int k=0;k<1<<n;k++){
        expand[k]=k;
        for(int x=1;x<=n;x++){
            if(k>>(x-1) & 1){//集合中存在点x 
                road[k][x]=0;
                for(int y=1;y<=n;y++){//枚举x可达的点 
                    if(a[x][y]==0x3f3f3f3f)continue;
                    expand[k]|=1<<(y-1);//将这个点加到集合中去,注意这个点可能已经在k中 
                    road[k][y]=min(road[k][y],a[x][y]);//到集合k的最小距离 
                }
            }
        } 
    }
    for(int j=0;j<1<<n;j++){
        for(int k=0;k<j;k++){//枚举j的子集,由于数量小,所以暴力枚举 
            if((k&j)==k && (expand[k]&j)==j){//k是j的子集并且j是expand[k]的子集 
                valid[j].push_back(k);//(k,j)合法 
                int sum=0;
                for(int i=1;i<=n;i++){
                    if((j^k)>>(i-1) & 1)sum+=road[k][i];//差集的最短路之和 
                } 
                cost[j].push_back(sum);
            }
        }
    }
}
int main(){
    cin>>n>>m;
    memset(a,0x3f,sizeof(a));
    for(int i=1;i<=m;i++){
        int x,y,z;
        cin>>x>>y>>z;
        a[x][y]=a[y][x]=min(a[x][y],z);
    }
    pre();
    memset(f,0x3f,sizeof f);
    for(int i=1;i<=n;i++)
        f[1][1<<(i-1)]=0;//扩展第一层不需要资金
        
    int ans=f[1][(1<<n)-1];
    for(int i=2;i<=n;i++){
        for(int j=1;j<1<<n;j++){
            for(int p=0;p<valid[j].size();p++){
                int k=valid[j][p];
                f[i][j]=min(f[i][j],f[i-1][k]+(i-1)*cost[j][p]);
            }
        } 
        ans=min(ans,f[i][(1<<n)-1]);
    }
        
    cout<<ans<<endl;
}
hmoban主题是根据ripro二开的主题,极致后台体验,无插件,集成会员系统
自学咖网 » 《算法竞赛进阶指南》0x56状态压缩DP AcWing529 宝藏