suxxsfe

一言(ヒトコト)

UOJ333【NOIP2017】宝藏

https://uoj.ac/problem/333
https://www.luogu.com.cn/problem/P3959

数据看一眼想到状压,一开始想了个错误的做法,就是先枚举起点,然后 \(f_s\) 表示被打通的点成为 \(s\) 状态最少花费多少,同时用一个 \(dis(s,i)\) 表示这个状态到各个点的距离
但是发现其实不是对的,因为前一个状态的最优不一定能推出后一个状态的最优
就是,可能我在选到 \(s\) 状态的时候通过某些方式少花了一些钱,但是少花钱的方式,比多花钱的方式造成当选到 \(s'\) 的时候,距离变得更长,到那时候需要花更多的钱。那如果前一个状态(\(s\))少花的前,小于了后一个状态(\(s'\))多花的钱,就造成了不够优
然后洛谷好像并没有卡掉这种做法,所以推荐去 UOJ 测

下面讲正解,还是枚举起点,由于前一个方法造成错误的原因是距离的不确定,那么此时不妨就让距离确定下来,用 \(f(S,deep)\) 表示 \(S\) 状态,树高(因为题目要求的实际上是最小生成树)为 \(deep\),的最小花费。由于确定了 \(deep\),那么下一步就是枚举深度是 \(deep+1\) 的点,每次把这写要选在 \(deep+1\) 层的点全上,在集合 \(T\) 中,然后枚举 \(T\) 的子集,转移:\(f(S,deep)\Rightarrow f(S|T,deep+1)\)

还有一个疑问,就是对于一个想要选入 \(T\) 的点,是会去让它和 \(S\) 中连边长度最短的一个点连边,那如何确定我选进 \(T\) 的点都是在 \(deep+1\) 层?
这样直接选的话即使一个点在图上画出来不是 \(deep+1\) 层,它在计算花费是依然是乘 \(depp\)
其实也不用考虑这个,假设那种点应该是 \(deep'<deep+1\) 层,它误被计算进了别的状态里,肯定就会导致那个状态不是最优,而把他选进 \(deep'\) 中的那个方案就会更优,从而在统计答案的时候就会统计那个方案
也就是对答案并没有影响

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
#include<iomanip>
#include<cstring>
#define reg register
#define EN puts("")
inline int read(){
	register int x=0;register int y=1;
	register char c=std::getchar();
	while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
	while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
	return y?x:-x;
}
#define N 14
int D[N][N];
int f[(1<<N)+10][N],sum[(1<<N)+10],dis[N];
int n,m;
inline int work(int start){
	int lim=1<<n;
	std::memset(f,0x3f,sizeof f);
	f[1<<(start-1)][1]=0;
	for(reg int S=0;S<lim;S++){
		std::memset(dis,0x3f,sizeof dis);
		int t=0;
		for(reg int i=1;i<=n;i++)if(S&(1<<(i-1))){
			for(reg int j=1;j<=n;j++)if(!(S&(1<<(j-1)))){
				if(D[i][j]==0x3f3f3f3f) continue;
				t|=(1<<(j-1));dis[j]=std::min(dis[j],D[i][j]);
			}
		}
		for(reg int T=t;T;T=(T-1)&t){//枚举一个集合的子集
			sum[T]=0;
			for(reg int i=1;i<=n;i++)
				if(T&(1<<(i-1))) sum[T]+=dis[i];
		}
		for(reg int deep=1;deep<=n;deep++){
			if(f[S][deep]==0x3f3f3f3f) continue;
			for(reg int T=t;T;T=(T-1)&t)
				f[S|T][deep+1]=std::min(f[S|T][deep+1],f[S][deep]+sum[T]*deep);
		}
	}
	int ans=1e9;
	for(reg int i=1;i<=n;i++) ans=std::min(ans,f[lim-1][i]);
	return ans;
}
int main(){
	n=read();m=read();
	std::memset(D,0x3f,sizeof D);
	for(reg int u,v,i=1;i<=m;i++){
		u=read();v=read();
		D[u][v]=D[v][u]=std::min(D[u][v],read());
	}
	int ans=1e9;
	for(reg int start=1;start<=n;start++) ans=std::min(ans,work(start));
	printf("%d",ans);
	return 0;
}
posted @ 2020-10-04 00:13  suxxsfe  阅读(141)  评论(0编辑  收藏  举报