【NOIP2013模拟】四叶草魔杖

Description

魔杖护法Freda融合了四件武器,于是魔杖顶端缓缓地生出了一棵四叶草,四片叶子焕发着淡淡的七色光。圣剑护法rainbow取出了一个圆盘,圆盘上镶嵌着N颗宝石,编号为0~N-1。第i颗宝石的能量是Ai。如果Ai>0,表示这颗宝石能量过高,需要把Ai的能量传给其他宝石;如果Ai<0,表示这颗宝石的能量过低,需要从其他宝石处获取-Ai的能量。保证sigma(Ai)=0。只有当所有宝石的能量均相同时,把四叶草魔杖插入圆盘中央,才能开启超自然之界的通道。

不过,只有M对宝石之间可以互相传递能量,其中第i对宝石之间无论传递多少能量,都要花费Ti的代价。探险队员们想知道,最少需要花费多少代价才能使所有宝石的能量都相同?

Input

第一行两个整数N、M。

第二行N个整数Ai。

接下来M行每行三个整数pi,qi,Ti,表示在编号pi和qi的宝石之间传递能量需要花费Ti的代价。数据保证每对pi、qi最多出现一次。

Output

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

Sample Input

3 3

50 -20 -30

0 1 10

1 2 20

0 2 100

Sample Output

30

Data Constraint

对于50%的数据,2<=N<=8。

对于100%的数据,2<=N<16,0<=M<=N*(N-1)/2,0<=pi,qi

Algorithm : 状压Dp, MST;

include:

1、找出所有能量为0的集合。若该集合的点是联通的,那么求出该集合的MST,生成树的值就是该集合能量转移所需的最小代价。
2、将每一集合为0的集合看成是一个物品,利用背包动态规划求出最优解。

specific:

用二进制来压缩状态,1代表节点在集合中,0代表不在。
比如数字s的二进制形式为100111,表明0,1,2,5号节点在s表示的集合中。
题目最多有n(n<=16)个节点,因此s的范围是0到(2^n)-1
用数组Sum[s],记录集合s中包含的节点的能量之和。
对于每一个能量和为0的集合x(Sum[x]==0),若能得到一棵最小生成树,用数组Value[x]记录下该生成树的代价
F[i]记录平衡集合i中的节点的能量值,所需最小代价
对于集合i和j,若满足Sum[i]==0且Sum[j]==0
那么有f[i|j]=min(f[i|j],f[i]+Cost[j]);
i|j表示集合i与集合j合并之后的集合

CODE

#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for (int i=a;i<=b;i++)
#define fd(i,a,b) for (int i=a;i>=b;i--)
#define N 20
#define Two 65536+5
#define INF 214748364
struct Record
{
    int x,y,z;
}a[N*(N-1)/2];
using namespace std;
int Dad[N],F[Two],Power[N],Sum[Two],Value[Two];
int n,m;
bool cmp(Record a,Record b)
{
    return a.z<b.z;
}
int get(int x)
{
    if (Dad[x]==x) return x;
    else Dad[x]=get(Dad[x]);
    return Dad[x];
}
int Kruskal(int s)
{
    int tot=0,Tree_tot=0,ans=0;
    fo(i,0,n-1) if ((s>>i)&1) tot++,Dad[i]=i;
    fo(i,1,m)
        if (((s>>a[i].x)&1) && ((s>>a[i].y)&1))
        {
            int xx=get(a[i].x);
            int yy=get(a[i].y);
            if (xx!=yy)
            {
                Dad[xx]=yy;
                Tree_tot++;
                ans+=a[i].z;    
            }
        }
    if (Tree_tot+1<tot) return INF;
    else return ans;
}
int main()
{
    //freopen("3392.in","r",stdin);
    scanf("%d%d",&n,&m);
    fo(i,0,n-1) scanf("%d",&Power[i]);
    fo(i,1,m) scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
    sort(a+1,a+m+1,cmp);
    int tot=(1<<n)-1;
    fo(i,0,tot)
        fo(j,0,n-1)
        if ((i>>j)&1) Sum[i]+=Power[j];
    fo(i,0,tot)
        if (!Sum[i]) Value[i]=Kruskal(i); 
        else Value[i]=INF;
    fo(i,0,tot) F[i]=INF;
    F[0]=0; 
    fo(i,0,tot)
        if (Sum[i]==0)
        fo(j,1,tot)
        {
            if (Sum[j]==0 && Sum[i]==0)
            F[i|j]=min(F[i|j],F[i]+Value[j]);
        }
    if (F[tot]==INF) printf("Impossible\n");
    else printf("%d\n",F[tot]);
    return 0;
}

Standard process

[cpp] view plain copy print?
#include <iostream>  
#include <cstdio>  
#include <algorithm>  
using namespace std;  
const int inf=1000000000;  
struct node{int x,y,z;}Edge[150];  
int n,m,A[17],Father[17],Sum[1<<17],Cost[1<<17],f[1<<17];  
bool cmp(node a,node b){ return a.z<b.z;}  
int GetFather(int x)  
{  
    if(x!=Father[x])Father[x]=GetFather(Father[x]);  
    return Father[x];  
}  
int Kruskal(int s)  //讨论状态s中包含的点,构成的最小生成树   
{  
    int i,MinCost=0,Cnt=0,tot=0,fx,fy;  
    for(i=0;i<n;i++)  
    {  
      Father[i]=i;                //初始化并查集   
      if((s>>i)&1)tot++;          //tot统计s状态中包含的节点数   
    }  

    for(i=1;i<=m;i++)  
      if(((s>>Edge[i].x)&1)&&((s>>Edge[i].y)&1))//判断状态s中是否包含了边i的两个端点   
      {  
          fx=GetFather(Edge[i].x);  
          fy=GetFather(Edge[i].y);  
          if(fx!=fy)  
          {  
              Father[fx]=fy;  
              Cnt++;               //Cnt记录生成树中节点的个数  
              MinCost+=Edge[i].z;   
          }  
      }  
    if(Cnt+1!=tot) return inf;   //若Cnt+1<tot表明有节点没加入到生成树,该集合不连通   
    else return MinCost;   
}  
int main()  
{  
    int i,j,Tot;  
    scanf("%d%d",&n,&m);  
    for(i=0;i<n;i++)scanf("%d",&A[i]);  
    for(i=1;i<=m;i++)scanf("%d%d%d",&Edge[i].x,&Edge[i].y,&Edge[i].z);  
    sort(Edge+1,Edge+m+1,cmp);  

    Tot=(1<<n)-1;         //总的状态数   
    for(i=0;i<=Tot;i++)   //讨论每一种状态表示的集合,记录其中节点的能量总和   
      for(j=0;j<n;j++)  
        if((i>>j)&1)Sum[i]+=A[j];   

    for(i=0;i<=Tot;i++)  
      if(Sum[i]==0)Cost[i]=Kruskal(i);  //若i表示的集合中,节点能量和为0,讨论其最小生成树   
      else Cost[i]=inf;  

    for(i=1;i<=Tot;i++)f[i]=inf;  
    f[0]=0;  

    for(i=0;i<=Tot;i++)  
    {  
        if(Sum[i]!=0)continue;  
        for(j=1;j<=Tot;j++)  
        {  
            if(Sum[j]!=0)continue;  
            f[i|j]=min(f[i|j],f[i]+Cost[j]);  
        }  
    }   

    if(f[Tot]==inf)printf("Impossible\n");else printf("%d\n",f[Tot]);   
    return 0;  
}  
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值