zoukankan      html  css  js  c++  java
  • 【状压DP】【P3959】【NOIP2017D2T2】宝藏

    Description

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

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

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

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

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

    $$mathrm{L} imes mathrm{K}$$

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

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

    Input

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

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

    Output

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

    Sample Input

    4 5 
    1 2 1 
    1 3 3 
    1 4 1 
    2 3 4 
    3 4 1 
    

    Sample Output

    4
    

    Hint

    对于$ 100\%$的数据: $1 le n le 12$,$0 le m le 1000$,$v le 500000$

    Solution

    看到数据范围就知道大概是个状压了。考虑一下怎么设计状态。

    看懂题意,题目的意思就是找一棵生成树,使得代价和最小。

    考虑在任意时刻,我们关心的只有我们已经把多少点加进生成树了,以及生成树的最大树高是多少。

    那么我们就设(f_{S,i})为当前生成树已经包含集合S中的点,并且树高是i。

    那么状态转移方程就出来了:

    (f_{S,i}=min){(f_{S_0,i-1}+pay)},其中满足(S_0)(S)的子集,通过(S_0)加边一定可以联结成(S)(pay)是这次加边的花费。

    那么怎么判断(S_0)在转移中是否合法呢?我们设(G_S)是S能拓展到的边的集合,显然G是可以预处理出来的。

    在考虑pay的计算。

    (ss=S~xor~S_0),即(ss)是在(S)但不在(S_0)中的元素。

    这里pay的计算显然是对于每个(ss)中的元素取(S_0)中的元素向它连一条最短的边求和后( imes)i。

    考虑这么做为什么是对的。

    对于一个集合S和(S_0),如果他们之间的边不是被(S_0)中最大深度的点连接成的,那么一定存在另一个(S_1),包含另一种连边的情况使得(S_1)包含除被最大深度点连接的点以外的所有点,那么通过(S_1)转移的答案就是最小值,一定是正确的。所以不会漏解。

    现在来整理一下思路:

    1、预处理出对于每个点的集合所能拓展的点的集合。

    2、对于每个点显然把他作为通向地面的点的时候他的深度是0,集合是((1<<i))。那么这样的DP值初始化为0

    3、枚举集合,对于每个集合的子集,通过(G_S)判断该子集是否合法,如果合法,枚举所有需要被连向的点的最小边权求和乘深度,作为答案。

    4、答案就是全集在1~n深度的最小值。

    下面考虑时间复杂度:

    首先考虑我们枚举集合的数量:
    我们集合的枚举量显然是全集的子集的子集个数和。乍一看全集有(2^n)个子集,每个子集有(2^n)个子集。那么个数是(O(4^n))?事实上不是这样。考虑对于所有的的子集,他们的子集个数是(O(2^k))个,其中(k)是子集的元素个数。那么对于大部分的子集,他们的元素个数k都不等于n。显然这个上界太松了。那么如何计算枚举量呢?考虑对于元素个数为x的子集,共有(C_{n}^x)种方式。每个子集有(2^x)个子集,那么总的枚举量是(C_{n}^x~ imes~2^x)。应用二项式定理,原式=((2+1)^n)=(3^n)。所以子集的枚举量是(O(3^n))

    对于每个集合最多有(n)个元素,每个元素枚举到它的边是(O(n))的,所以这里的复杂度是(O(n^2))的。

    那么总的复杂度是(O(3^n~ imes~n^2))。大概是七千万左右。考虑对于绝大部分集合跑不满(n^2)的上界,并且可以进行一些剪枝优化,最终可以通过本题

    Code

    为了二进制运用方便,读入点的时候减一,所有的点从0编号到n-1。

    #include<cstdio>
    #include<cstring>
    #define rg register
    #define ci const int
    #define cl const long long int
    
    typedef long long int ll;
    
    namespace IO {
    	char buf[50];
    }
    
    template<typename T>
    inline void qr(T &x) {
    	char ch=getchar(),lst=' ';
    	while(ch>'9'||ch<'0') lst=ch,ch=getchar();
    	while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    	if (lst=='-') x=-x;
    }
    
    template<typename T>
    inline void write(T x,const char aft,const bool pt) {
    	if(x<0) {putchar('-');x=-x;}
    	int top=0;
    	do {
    		IO::buf[++top]=x%10+'0';
    		x/=10;
    	} while(x);
    	while(top) putchar(IO::buf[top--]);
    	if(pt) putchar(aft);
    }
    
    template <typename T>
    inline T mmax(const T a,const T b) {if(a>b) return a;return b;}
    template <typename T>
    inline T mmin(const T a,const T b) {if(a<b) return a;return b;}
    template <typename T>
    inline T mabs(const T a) {if(a<0) return -a;return a;}
    
    template <typename T>
    inline void mswap(T &a,T &b) {T temp=a;a=b;b=temp;}
    
    
    const int maxn = 15;
    const int maxm = 1010;
    const int maxt = 1<<maxn;
    const int INF = 0x3f3f3f3f;
    
    int n,m,a,b,c,ans=INF;
    int frog[maxt][maxn],gorf[maxt],dis[maxn][maxn];
    
    int main() {
    	qr(n);qr(m);
    	memset(dis,0x3f,sizeof dis);
    	for(rg int i=1;i<=m;++i) {
    		a=b=c=0;qr(a);qr(b);qr(c);--a;--b;
    		dis[b][a]=dis[a][b]=mmin(dis[a][b],c);
    	}
    	memset(frog,0x3f,sizeof frog);
    	int all=(1<<n)-1;
    	for(rg int i=1;i<=all;++i) {
    		for(rg int j=0;j<n;++j) if(((1<<j)|i) == i) {
    			dis[j][j]=0;
    			for(rg int k=0;k<n;++k) if(dis[j][k]!=INF) {
    				gorf[i]|=(1<<k);
    			}
    		}
    	}
    	for(rg int i=0;i<n;++i) frog[1<<i][0]=0;
    	for(rg int i=2;i<=all;++i) {
    		for(rg int s0=i-1;s0;s0=(s0-1)&i) if((gorf[s0]|i) == gorf[s0]) {
    			int _sum=0;
    			int ss=s0^i;
    			for(rg int k=0;k<n;++k) if((1<<k)&ss) {
    				int _temp=INF;
    				for(rg int h=0;h<n;++h) if((1<<h)&s0) {
    					_temp=mmin(_temp,dis[h][k]);
    				}
    				_sum+=_temp;
    			}
    			for(rg int j=1;j<n;++j) if(frog[s0][j-1]!=INF) {
    				frog[i][j]=mmin(frog[i][j],frog[s0][j-1]+_sum*j);
    			}
    		}
    	}
    	for(rg int i=0;i<n;++i) ans=mmin(ans,frog[all][i]);
    	write(ans,'
    ',true);
    	return 0;
    }
    

    Summary

    1、n个元素的子集的子集数量是(3^n)
    2、在进行状态设计时可以考虑对于每个时刻需要知道的信息,以此设计维度
    3、预处理大法吼啊!

  • 相关阅读:
    Android 关于屏幕适配
    android 数据存储之SharePreference 的几种方式
    Android多项目依赖在Eclipse中无法关联源代码的问题解决 Ctril 点不进去的解决方法
    android onIntent 是什么东西
    Android一次退出所有Activity的方法(升级版)
    Android 一次退出所有activity的方法
    android 获取屏幕尺寸
    自定义view(自定义view的时候,三个构造函数各自的作用)
    Android应用自动更新功能的实现!!!
    android实现透明和半透明效果
  • 原文地址:https://www.cnblogs.com/yifusuyi/p/9549886.html
Copyright © 2011-2022 走看看