【NOIP2013模拟】四叶草魔杖

本文解析了一道NOIP2013模拟赛题“四叶草魔杖”,介绍了如何通过集合划分与最小生成树算法解决宝石能量平衡问题。题目要求在特定条件下调整宝石间的能量传递,以实现所有宝石能量一致的目标。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【NOIP2013模拟】四叶草魔杖

Time Limits: 1000 ms Memory Limits: 131072 KB

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< N,-1000<=Ai<=1000,0<=Ti<=1000,ni=1Ai=0。


解题思路
我们可以找到几个集合,每个集合点的权值和为0。
那么我们可以将每个集合求出最小生成树的距离,标记到一个数组f里,用二进制状态压缩储存,对于每两个编号,x,y,用f[x]+f[y]更新f[x xor y],存入x xor y。

var father:array[0..16]of shortint;
    f:array[0..65536] of longint;
    v:array[0..16]of longint;
    a:array[0..1000,1..3]of longint;
    t:array[0..1000000]of longint;
    i,n,m,top,j:longint;
function get(x:longint):longint;
begin
    if father[x]=x then exit(x);
    father[x]:=get(father[x]);
    exit(father[x]);
end;
procedure qsort(h,t:longint);
    var l,r,m:longint;
begin
    l:=h; r:=t;
    m:=a[(h+t)div 2,3];
    repeat
        while a[l,3]<m do inc(l);
        while a[r,3]>m do dec(r);
        if l<=r then
        begin
            a[0]:=a[l]; a[l]:=a[r]; a[r]:=a[0];
            inc(l); dec(r);
        end;
    until l>r;
    if h<r then qsort(h,r);
    if l<t then qsort(l,t);
end;
function check(what,tot:longint):longint;
    var o,x,y,num,dis:longint;
begin
    if tot=1 then exit(0);
    for o:=1 to n do father[o]:=o;
    num:=0; dis:=0;
    for o:=1 to m do  //做最小生成树
    begin
        if ((1 shl (a[o,1]-1))and what)>0 then
            if ((1 shl (a[o,2]-1))and what)>0 then
            begin
                x:=get(a[o,1]); y:=get(a[o,2]);
                if x<>y then
                begin
                    father[x]:=father[y];
                    inc(num); inc(dis,a[o,3]);
                    if num=tot-1 then exit(dis);
                end;
            end;
    end;
    exit(maxlongint);
end;
procedure find(x,tot,what,sum:longint);  //寻找权值和为0的点集
    var o:longint;
begin
    if sum=0 then
    begin
        f[what]:=check(what,tot);  //检查集合是否连通
        if f[what]<maxlongint then
        begin
            inc(top);
            t[top]:=what;
        end;
    end;
    if x>n then exit;
    for o:=x to n do find(o+1,tot+1,what+1 shl (o-1),sum+v[o]);  //参数传递用二进制状态压缩
end;
procedure print;
    var x,y:longint;
begin
    i:=0;
    while i<top do
    begin
        inc(i);
        for j:=i+1 to top do
        begin
            x:=f[t[i]]+f[t[j]];  //更新
            y:=t[i] xor t[j];
            if x<f[y] then
            begin
                inc(top);
                t[top]:=y; f[y]:=x;  //存入队列
            end;
        end;
    end;
    if f[1 shl n-1]=maxlongint then writeln('Impossible')else writeln(f[1 shl n-1]);  
end;
begin
    read(n,m);
    for i:=1 to n do read(v[i]);
    for i:=1 to m do
    begin
        read(a[i,1],a[i,2],a[i,3]);
        inc(a[i,1]); inc(a[i,2]);
    end;
    qsort(1,m);
    for i:=0 to 1 shl n do f[i]:=maxlongint;
    find(1,0,0,0);
    print;
end.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值