TYVJ 2054 [Nescafé29]四叶草魔杖 最小生成树 状态压缩/背包DP

$ ightarrow $ 戳我进TYVJ原题

[Nescafé29]四叶草魔杖

题目限制

时间限制 内存限制 评测方式 题目来源
1000ms 131072KiB 标准比较器 Local

 

题目背景

陶醉在彩虹光芒笼罩的美景之中,探险队员们不知不觉已经穿过了七色虹,
到达了目的地,面前出现了一座城堡和小溪田园,城堡前的木牌上写着“Poetic Island”。
 
“这一定就是另外两位护法的所在地了……我们快进去吧!”
 
探险队员们快步进入了城堡,城堡大厅的羊毛沙发上坐着两个人。
 
“你们是Nescafe的护法吧?”
 
“是的哦~ 我们就是圣剑护法rainbow和魔杖护法freda~ 你们来这里做什么呢~”
 
“我们是来拜访圣主和四位护法的……”
 
“可是圣主applepi已经前往超自然之界的学校(Preternatural Kingdom University,简称PKU)修炼魔法了,
要想见到他,必须开启Nescafe之塔与超自然之界的通道。但是圣主规定,开启通道的方法不能告诉任何外人。
我只能提示你们,开启通道的钥匙就与四位护法有关T_T”
 
探险队员环视四周,突然,其中一人的目光停留在了魔杖之上。
“hoho~ 魔杖!传说中开启异时空通道的钥匙不就叫四叶草魔杖吗?
四叶草有力量、信心、希望和幸运四片叶子,护法恰好有神刀、飞箭、圣剑、魔杖四位!aha~我找到答案了!”
 
“好吧,那我们就满足你们的愿望~”
 

题目描述

魔杖护法Freda融合了四件武器,于是魔杖顶端缓缓地生出了一棵四叶草,四片叶子幻发着淡淡的七色光。
圣剑护法rainbow取出了一个圆盘,圆盘上镶嵌着 $ N $ 颗宝石,编号为 $ 0~N-1 $ 。
第i颗宝石的能量是 $ A_i $ 。
如果 $ A_i>0 $ ,表示这颗宝石能量过高,需要把Ai的能量传给其它宝石;
如果 $ A_i<0 $ ,表示这颗宝石的能量过低,需要从其它宝石处获取 $ -A_i $ 的能量。
保证 $ ∑A_i =0 $ 。只有当所有宝石的能量均相同时,把四叶草魔杖插入圆盘中央,才能开启超自然之界的通道。
不过,只有 $ M $ 对宝石之间可以互相传递能量,其中第 $ i $ 对宝石之间无论传递多少能量,都要花费 $ T_i $ 的代价。
探险队员们想知道,最少需要花费多少代价才能使所有宝石的能量都相同?
 

输入格式

第一行两个整数 $ N、M $ 。
第二行 $ N $ 个整数 $ A_i $ 。
接下来 $ M $ 行每行三个整数 $ p_i,q_i,T_i $ ,表示在编号为 $ p_i $ 和 $ q_i $ 的宝石之间传递能量需要花费 $ T_i $ 的代价。
数据保证每对 $ p_i、q_i $ 最多出现一次。
 

输出格式

输出一个整数表示答案。无解输出Impossible。
 

提示

对于 $ 50 $ % 的数据,$ 2 le N le 8 $ 。
对于 $ 100 $ % 的数据,$ 2 le N le 16,0 le M le N*(N-1)/2,0 le p_i,q_i<N,-1000 le A_i le 1000,0 le T_i le 1000,∑A_i=0 $ 。
 

样例数据

输入样例

 3 3
 50 -20 -30
 0 1 10
 1 2 20
 0 2 100

输出样例

 30

 

题解

  • 每个 $ sum A_i =0 $ 的子图内传递能量的最小代价是其最小生成树

  • 最终整个图可能分成若干块 $ sum a_i =0 $ 的子图分别传递

  • 以 $ sum a_i =0 $ 的子图为物品做二进制集合背包的状态压缩 $ DP $
     

代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
#define inf 1000000007
struct edge{ int u,v,w; }e[205];
int n,m,a[20],f[1<<20],g[1<<20],fa[20],tot;
bool vis[20];
bool cmp(edge x,edge y){ return x.w<y.w; }
int calc(int x){
	int res=0;
	for(int i=1;i<=n;++i) if(x&(1<<i-1)) res+=a[i];
	return res;
}
int find(int x){
	if(fa[x]!=x) fa[x]=find(fa[x]);
	return fa[x];
}
int kruskal(int x){
	int num=0,res=0,tmp=0;
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n;++i){
		fa[i]=i;
		if(x&(1<<(i-1))) vis[i]=1,++num;
	}
	for(int fu,fv,i=1;i<=m;++i){
		if(!vis[e[i].u]||!vis[e[i].v]) continue;
		fu=find(e[i].u); fv=find(e[i].v);
		if(fu==fv) continue;
		fa[fu]=fv;
		res+=e[i].w;
		++tmp; if(tot==num-1) break;
	}
	if(tmp!=num-1) return inf;
	return res;
}
int main(){
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	for(int i=1;i<=m;++i){
		scanf("%d %d %d",&e[i].u,&e[i].v,&e[i].w);
		++e[i].u; ++e[i].v;
	}
	sort(e+1,e+1+m,cmp);
	memset(f,0x3f,sizeof(f));
	f[0]=0;
	for(int i=1;i<=(1<<n)-1;++i)
		if(calc(i)==0){
			int tmp=kruskal(i);
			if(tmp!=inf) g[++tot]=i; f[i]=tmp;
		}
	for(int i=0;i<=(1<<n)-1;++i)
		for(int j=1;j<=tot;++j)
			if((i&g[j])==0) f[i|g[j]]=min(f[i|g[j]],f[i]+f[g[j]]);
	if(f[(1<<n)-1]==inf) puts("Impossible");
	else printf("%d",f[(1<<n)-1]);
	return 0;
}
原文地址:https://www.cnblogs.com/PotremZ/p/TYVJ2054.html