P3959 [NOIP2017 提高组] 宝藏

Posted Jozky86

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P3959 [NOIP2017 提高组] 宝藏相关的知识,希望对你有一定的参考价值。

P3959 [NOIP2017 提高组] 宝藏

题意:

额题意不好说,就是n个点m个边,选定一个点为根节点,构造一个最小生成树,边的权值为该该边起点到根节点之间的点的数量K(不含根节点) * 道路长度
1<=n<=12
0<=m<=1e3
v<=5e5

题解:

参考题解
这数据范围?暴力暴力暴力
不,我们用状压dp来做
我们设dp[i][j]表示用到第i个元素,当前连接状态为j的花费的最小值
这个式子没办法直接转移,因为每个边的花费是不一样的,即k是不一样的,我们可以重新设计一个状态,我们将k值理解为距离初始化点的层数,如图
在这里插入图片描述
被涂蓝色的就是根节点,k就是划分的层数
这样我们设dp[i][j]表示到第i层,总共取了的点的状态为j
转移为:
dp[i][j] = min(dp[i-1][k]+trans[k][j] * (i-1))
trans[k][j] * (i-1)就是题目说的距离 * K(题目中说的k)
k是j的子集,即有可能转移到j的状态
trans[k][j]表示从状态k转移到状态j的最小花费路径
这个子集意思就是:sub就是S的子集
这个子集怎么求??
直接求2^n必然不行,会T,有小技巧
由公式:

for (int sub = S; sub; sub = (sub - 1) & S) {
	// sub 为 S 的子集
}

证明过程
在这里插入图片描述
最终答案就是:min(dp[i][2n-1])
初始化:dp[1][2(i-1)] = 0 (i∈[1,n])
我感觉代码很妙,思路也很妙,让我想是真写不出来
我详细说trans如何求:
现在i是当前状态,j是i的子状态,我们现在要状态转移从j–>i
temp=i ^ j,即要转移的点(因为 ^ 为不同为1)
然后我们开始枚举temp中存在的点k(从高位往低位),然后求从k到j的最短距离tmin,把tmin加入到trans[j][i]中

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
long long read()
{
    long long x=0,f=1; char c=getchar();
    while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
    while(isdigit(c)){x=x*10+c-'0';c=getchar();}
    return x*f;
}
const int N=12+2;
const int M=1<<N;
int n,m,dis[N][N],trans[M][M],POW[N];
long long f[N][M];
int main()
{
    n=read(),m=read();
    memset(dis,0x3f,sizeof dis);
    for(int i=1;i<=m;i++)
    {
        int s=read(),t=read(),v=read();
        if(dis[s][t]>v)
            dis[s][t]=dis[t][s]=v;
    }
    for(int i=0;i<(1<<n);i++)
        for(int j=i;j!=0;j=(j-1)&i)
        {
            bool OK=true;
            int temp=i^j;//状态i与状态j的不同之处,状态转移为j->i 
            for(int k=n-1;k>=0;k--)
            {
            	if((temp>>k)&1)//说明点k是转移中增加的点,即 j没有,i有 
                {
                    int tmin=0x3f3f3f3f;
                    for(int L=1;L<=n;L++)
                    	if(1&(j>>(L-1)))//如果状态j包含此点 ,求出L到k+1的最短距离
                        //if(((1<<(L-1))&j)==(1<<(L-1))) 
                            tmin=min(tmin,dis[L][k+1]);
                            
                    if(tmin==0x3f3f3f3f)//如果此路无法走通 
                    {
                        OK=false;
                        break;
                    }
                    
                    trans[j][i]+=tmin;
                    /*
                    相当于把j到i这段路拆分成了好几份 
					*/ 
                    temp-=(1<<k);
                }
			}
            if(OK==false)
                trans[j][i]=0x3f3f3f3f;
        }
        
    memset(f,0x3f,sizeof f);
    for(int i=1;i<=n;i++)
        f[1][(1<<(i-1))]=0;
        
    for(int i=2;i<=n;i++)
        for(int j=0;j<(1<<n);j++)
            for(int k=j;k!=0;k=(k-1)&j)//k为j的子状态,也就是k是j的子集 
                if(trans[k][j]!=0x3f3f3f3f)//说明可以从状态k到j 
                    f[i][j]=min(f[i][j],f[i-1][k]+(i-1)*trans[k][j]);

    long long ans=0x3f3f3f3f3f3f3f3fll;
    for(int i=1;i<=n;i++)
        ans=min(ans,f[i][(1<<n)-1]);
    printf("%lld",ans);
    return 0;
}

以上是关于P3959 [NOIP2017 提高组] 宝藏的主要内容,如果未能解决你的问题,请参考以下文章

luogu P3959 宝藏

P3959 宝藏

P3959 宝藏

题解P3959 宝藏 - 状压dp / dfs剪枝

题解 P3959 宝藏

P3959 宝藏 状压dp