zoukankan      html  css  js  c++  java
  • [2019 CSP-S赛前集训] [P3959] [蒟蒻Xx_queue学DP] 3.宝藏

    蒟蒻Xx_queue又来更博客啦!!!

    话说之前一直都没有讲到与状压DP枚举子集有关的知识,那我们今天就来补充一下;

    今天要讲的题目是:

    [NOIP2017TG] 宝藏


    题目传送门

    题目描述

    参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了(n)个深埋在地下的宝藏屋, 也给出了这(n)个宝藏屋之间可供开发的(m)条道路和它们的长度。

    小明决心亲自前往挖掘所有宝藏屋中的宝藏。但是,每个宝藏屋距离地面都很远, 也就是说,从地面打通一条到某个宝藏屋的道路是很困难的,而开发宝藏屋之间的道路 则相对容易很多。

    小明的决心感动了考古挖掘的赞助商,赞助商决定免费赞助他打通一条从地面到某 个宝藏屋的通道,通往哪个宝藏屋则由小明来决定

    在此基础上,小明还需要考虑如何开凿宝藏屋之间的道路。已经开凿出的道路可以任意通行不消耗代价。每开凿出一条新道路,小明就会与考古队一起挖掘出由该条道路 所能到达的宝藏屋的宝藏。另外,小明不想开发无用道路,即两个已经被挖掘过的宝藏屋之间的道路无需再开发

    新开发一条道路的代价是:

    (mathrm{L} imes mathrm{K})

    (L)代表这条道路的长度,(K)代表从赞助商帮你打通的宝藏屋到这条道路起点的宝藏屋所经过的 宝藏屋的数量(包括赞助商帮你打通的宝藏屋和这条道路起点的宝藏屋) 。

    请你编写程序为小明选定由赞助商打通的宝藏屋和之后开凿的道路,使得工程总代 价最小,并输出这个最小值

    输入格式

    第一行两个用空格分离的正整数 (n),(m),代表宝藏屋的个数和道路数。

    接下来 (m) 行,每行三个用空格分离的正整数,分别是由一条道路连接的两个宝藏 屋的编号(编号为 (1 - n)),和这条道路的长度 (v)

    输出格式

    一个正整数,表示最小的总代价。

    输入输出样例

    输入 1
    4 5
    1 2 1
    1 3 3
    1 4 1
    2 3 4
    3 4 1

    输出 1
    4

    输入 2
    4 5
    1 2 1
    1 3 3
    1 4 1
    2 3 4
    3 4 2
    输出 2
    5

    说明/提示

    【样例解释1】

    小明选定让赞助商打通了(1) 号宝藏屋。小明开发了道路 (1 o 2),挖掘了 (2) 号宝 藏。开发了道路 (1 o 4),挖掘了 (4) 号宝藏。还开发了道路 (4 o 3),挖掘了(3)号宝 藏。工程总代价为:(1 imes 1 + 1 imes 1 + 1 imes 2 = 4)

    【样例解释2】

    小明选定让赞助商打通了(1) 号宝藏屋。小明开发了道路 (1 o 2),挖掘了 (2) 号宝 藏。开发了道路 (1 o 3),挖掘了 (3) 号宝藏。还开发了道路 (1 o 4),挖掘了(4)号宝 藏。工程总代价为:(1 imes 1 + 3 imes 1 + 1 imes 1 = 5)

    【数据规模与约定】

    对于20%的数据: 保证输入是一棵树(1≤n≤8)(v≤5000) 且所有的 (v)都相等。

    对于 40%的数据: (1≤n≤8)(0≤m≤1000)(v≤5000) 且所有的(v)都相等。

    对于70%的数据: (1≤n≤8)(0≤m≤1000)(v≤5000)

    对于100%的数据: (1≤n≤12)(0≤m≤1000)(v≤500000)


    分析:

    先看数据范围:(1≤n≤12),不是爆搜就是状压DP;(爆搜加玄学剪枝当然可以过,详解见洛谷题解)

    (玄学的模拟退火就算了,不知道你们有没有想到最小生成树,然而是行不通的)

    这里我就来讲一讲状压DP的做法,毕竟前面我才刚刚写了一个状压DP的学习总结嘛(这回不是玉米田了);

    因为题目中说到:新开发一条道路的代价是:

    (mathrm{L} imes mathrm{K})

    (L)代表这条道路的长度,(K)代表从赞助商帮你打通的宝藏屋到这条道路起点的宝藏屋所经过的 宝藏屋的数量(包括赞助商帮你打通的宝藏屋和这条道路起点的宝藏屋。

    所以这个题目应该和你打通的数的层数有关系(因为你需要计算K啊);

    所以不妨设(f_{i,j})表示已经打通的宝藏屋中的最大深度为(i),宝藏屋打通的状态(状压,二进制,大概明白是什么意思吧?)为(j),

    (j)的每一位中1代表这个宝藏屋已经打通了,0表示没有打通;

    现有的状态可以由 这个状态的子集+新打通的一些宝藏屋 转移过来

    所以比较容易得出状态转移方程就是(f_{i,j}=min{f_{i-1,k}+(i-1)*c_{k,j}})

    其中(i)为深度,(j)为目标状态,(k)(j)的子集,(c_{k,j})代表从状态(k)转移至(j)的花费(也就是把集合(j)的子集(k)添加一部分元素变成集合(j)所需要的代价)

    蒯了一个图,大家看一看:(来自洛谷题解)

    大概就是这样吧,不知道你搞懂了吗??

    这里注意一些细节:我们需要预处理出从状态(k)转移至(j)的花费(下面的代码中用trans数组储存),还要记得开long long!

    具体细节以及实现过程请研究代码:


    AC代码:

    #include <bits/stdc++.h>
    #define N 13
    #define INF 0x3f3f3f3f
    using namespace std;
    int n,m;
    int dis[N][N];//两个宝藏屋之间的距离
    int trans[1<<N][1<<N];//从一个状态转移至另一个状态(从集合的子集转移至该集合)的花费
    long long dp[N][1<<N];//dp[i][j],i表示深度,j表示状态(二进制数)
    void gettrans(){//取得转移花费
    	int siz=(1<<n);
    	for(int i=0;i<siz;i++){
    		for(int j=i;j!=0;j=(j-1)&i){//枚举i的子集
    			bool rightt=true;
    			int now=i^j;//now为i-j(集合相减),得到的是j中不含而i中含有的元素
    			for(int k=n;k>=1;k--){//枚举集合now的每一位
    				if(now>=(1<<(k-1))){//now的第k位是否为1
    					int minn=INF;
    					for(int l=1;l<=n;l++)
    						if(((1<<(l-1))&j)==(1<<(l-1))) minn=min(minn,dis[l][k]);//求出转移花费
    					if(minn==INF){
    						rightt=false;
    						break;
    					}
    					trans[j][i]+=minn;
    					now-=(1<<(k-1));//继续求
    				}
    			}
    			if(rightt==false) trans[j][i]=INF;//转移不了视为inf
    		}
    	}
    }
    void DP(){//dp
    	int siz=(1<<n);
    	memset(dp,0x3f,sizeof(dp));//初始化为inf
    	for(int i=1;i<=n;i++){
    		dp[1][1<<(i-1)]=0;//只有一层,则为0(免费开采第一层)
    	}
    	for(int i=2;i<=n;i++){
    		for(int j=0;j<siz;j++){
    			for(int k=j;k!=0;k=(k-1)&j){//枚举子集
    				if(trans[k][j]!=INF) dp[i][j]=min(dp[i][j],dp[i-1][k]+trans[k][j]*(i-1));//转移方程(前面详细讲过了,不知道的回炉重造)
    			}
    		}
    	}
    }
    int main(){
    	scanf("%d%d",&n,&m);
    	memset(dis,0x3f,sizeof(dis));
    	for(int i=1;i<=m;i++){//读入不用解释
    		int u,v,wei;
    		scanf("%d%d%d",&u,&v,&wei);
    		if(wei<dis[u][v]){
    			dis[u][v]=dis[v][u]=wei;
    		}
    	}
    	gettrans();
    	DP();
    	long long ans=0x3f3f3f3f3f3f3f3f;
    	int siz=(1<<n);
    	for(int i=1;i<=n;i++) ans=min(ans,dp[i][siz-1]);//统计答案
    	printf("%lld",ans);
    	return 0;
    }
    

    怎么样?除了玉米田,可见枚举子集也是非常非常重要的(当然还有集合的基本运算),相信你弄懂这道题目后对于状压DP有更加理解了;

    还是要多多练习啊!


    转载请注明出处--Xx_queue
  • 相关阅读:
    selenium的持续问题解决
    为什么使用Nginx
    [转]性能测试场景设计深度解析
    [转]CentOS7修改时区的正确姿势
    [转]利用Fiddler模拟恶劣网络环境
    [转]什么是微服务
    [转] WebSocket 教程
    [转] Python实现简单的Web服务器
    shell修改配置文件参数
    [转] linux shell 字符串操作(长度,查找,替换)详解
  • 原文地址:https://www.cnblogs.com/Xx-queue/p/11755817.html
Copyright © 2011-2022 走看看