[Noip2017]宝藏

[Noip2017]宝藏

一.前言

​ 这道题的转移是真的有点怪……题目链接

二.思路

​ 嘛,很明显是让你造一个生成树使得代价最小,看到 (1<=n<=12) 的时候就应该明白这道题的疯狂暗示了,状压 DP 嘛,TG老朋友了。

​ 观察到代价的计算,会发现一个 (K) 是一个至关重要的点,不把这个处理好了,就做不了。而这个 K也可以理解为深度。那么将状态设为 (f_{ij}) 为用状态为 j 的集合 (S) 构造出了一个最深深度为 i 的代价最小的生成树。

​ 然后很多题解所谓的"显而易见"的方程我反正是不能一下子想出来的,所以我会写的麻烦一些。计算 (f_{ij}) 那么

  • 肯定存在一个生成树代表 (f_{ij}) 的最优状态,它的点集设为 (S)
  • 这颗树一定有至少一个深度为 i 的点,这些点的点集我设为 (u)
  • 点集中所有点的父亲深度都为 (i-1)
  • 设一个树 (p) 为 这个生成树 删去 (u) 后的模样,其状态为 q

于是 p 必然代表 (f_{i-1~q}) 的最优状态(措辞有点不严谨了见谅),抽象的说就是 最优减去必要的最少后还是最优,建议反复理解。于是可以反着来。

​ 枚举最深深度 i 从小到大,再枚举状态 j 和 j 的子集 k,可以得到转移方程

[f_{ij}=min(f_{ij},f_{i-1~k}+trans_{jk}*(i-1)) ]

其中 (trans) 为状态转移的最短距离和。实际上枚举子集 k 就是在找 u(u=j^k) ,由于 i 从小到大之前的状态都已经是最优,放心转移就好。

​ 然后初始化就是最深为1,状态只有一个点的时候老板帮忙,无花费,为 0。

​ 这题我当时觉得很奇怪的一点就在于,每一个状态在没有算完的时候是可能不合法的,因为 (trans)不会全部都接到最深的点,枚举的集合也不会都是最外围的点,但是最后最小的一定是合法的就对了。最优对最优的时候一定全部接到最深的点上,因为定义就是这么定义的,找的集合是最优时候的(上面的u)如果接其他点就不会是最优……(有点因果律)不像其他的 DP 题,很多时候只是不是最优,但是不会是不合法。

​ 以上。虽并非什么真知灼见,却也还是若有所思。

三.CODE

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<fstream>
#include<cmath>
#include<cstring>
using namespace std;
int read(){
	char ch=getchar();
	int res=0,f=1;
	for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
	for(;ch>='0'&&ch<='9';ch=getchar())res=res*10+(ch-'0');
	return res*f;
}
const int MAXN=1<<12,inf=707406378;
int n,maxn,m;
int trans[MAXN][MAXN],dis[13][13],f[13][MAXN];
int main(){
	memset(dis,127/3,sizeof(dis));
	memset(f,127/3,sizeof(f));
	n=read();m=read();
	maxn=1<<n;
	for(int i=1,x,y,v;i<=m;++i){
		x=read()-1;y=read()-1;v=read();
		dis[x][y]=dis[y][x]=min(dis[x][y],v);
	}
	for(int i=0;i<maxn;++i){
		for(int j=i-1;j;j=(j-1)&i){
			int k=i^j,stj[13],topj=0,topk=0,stk[13];
			for(int p=0;p<n;++p){
				if(j&(1<<p))stj[++topj]=p;
				if(k&(1<<p))stk[++topk]=p;
			}
			while(topk){
				int cnt=inf;
				for(int p=topj;p>0;--p){
					cnt=min(cnt,dis[stk[topk]][stj[p]]);
				}
				if(cnt!=inf)trans[i][j]+=cnt;
				else{
					trans[i][j]=inf;
					break;
				} 
				topk--;
			}
		}
	}
	int ans=inf;
	for(int i=0;i<n;++i)f[1][1<<i]=0;
	ans=f[1][maxn-1];
	for(int k=2;k<=n;++k){
		for(int i=1;i<maxn;++i){
			for(int j=i-1;j;j=(j-1)&i){
				if(trans[i][j]!=inf&&f[k-1][j]!=inf)
					f[k][i]=min(f[k][i],f[k-1][j]+(k-1)*trans[i][j]);
			}
		}
		ans=min(ans,f[k][maxn-1]);
	}
	cout<<ans;
	return 0;
} 

trans整的比较粗糙hhhh

原文地址:https://www.cnblogs.com/clockwhite/p/13493335.html