[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