1. 程式人生 > >NKOJ 2439 四葉草魔杖(最小生成樹+狀壓dp/網路流)

NKOJ 2439 四葉草魔杖(最小生成樹+狀壓dp/網路流)

2439 四葉草魔杖

問題描述
  
   魔杖護法Freda融合了四件武器,於是魔杖頂端緩緩地生出了一棵四葉草,四片葉子幻發著淡淡的七色光。聖劍護法rainbow取出了一個圓盤,圓盤上鑲嵌著N顆寶石,編號為0~N-1。第i顆寶石的能量是Ai。如果Ai>0,表示這顆寶石能量過高,需要把Ai的能量傳給其它寶石;如果Ai<0,表示這顆寶石的能量過低,需要從其它寶石處獲取-Ai的能量。保證∑Ai =0。只有當所有寶石的能量均相同時,把四葉草魔杖插入圓盤中央,才能開啟超自然之界的通道。
不過,只有M對寶石之間可以互相傳遞能量,其中第i對寶石之間無論傳遞多少能量,都要花費Ti的代價。探險隊員們想知道,最少需要花費多少代價才能使所有寶石的能量都相同?

輸入格式

第一行兩個整數N、M。
第二行N個整數Ai。
接下來M行每行三個整數pi,qi,Ti,表示在編號為pi和qi的寶石之間傳遞能量需要花費Ti的代價。資料保證每對pi、qi最多出現一次。

輸出格式

輸出一個整數表示答案。無解輸出Impossible

樣例輸入

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

樣例輸出

30

提示

對於 50% 的資料,2<=N<=8。
對於 100% 的資料,2<=N<=16,0<=M<=N*(N-1)/2,0<=pi,qi<N,-1000<=Ai<=1000,0<=Ti<=1000,∑Ai=0。

做法一:最小生成樹+狀壓dp

列舉點集,若當前點集構成聯通塊且能量之和為0,顯然當前聯通塊傳遞能量的最小代價是其最小生成樹,因此將每個這樣的聯通塊看成一個物品,揹包dp算出最小費用即可。

程式碼:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
struct node{int x,y,z;};
bool cmp(node a,node b)
{return a.z<b.z;}
int n,m,A[20
],S[66666],V[66666],F[66666],TOT; int fa[20]; bool mark[20]; node P[200]; int GF(int x) { if(fa[x]!=x)fa[x]=GF(fa[x]); return fa[x]; } int Kruscal(int s) { int i,j,fx,fy,x,y,k=1,tot=0,cnt=0,ans=0; memset(mark,0,sizeof(mark)); for(i=1;i<=n;i++)if((1<<i-1)&s)tot++,mark[i]=1; for(i=1;i<=n;i++)fa[i]=i; while(k<=m&&cnt<tot) { x=P[k].x; y=P[k].y; fx=GF(x);fy=GF(y); if(mark[x]&&mark[y]&&fx!=fy) { ans+=P[k].z; fa[fx]=fy; cnt++; } k++; } if(cnt+1<tot)return 1e9; return ans; } int main() { int i,j; scanf("%d%d",&n,&m); for(i=1;i<=n;i++)scanf("%d",&A[i]); for(i=1;i<=m;i++) { scanf("%d%d%d",&P[i].x,&P[i].y,&P[i].z); P[i].x++;P[i].y++; } sort(P+1,P+m+1,cmp); TOT=(1<<n)-1; for(i=1;i<=TOT;i++) for(j=1;j<=n;j++)if((1<<j-1)&i)S[i]+=A[j]; for(i=1;i<=TOT;i++)if(S[i]==0)V[i]=Kruscal(i); for(i=1;i<=TOT;i++)F[i]=1e9;F[0]=0; for(i=0;i<=TOT;i++) { if(S[i])continue; for(j=0;j<=TOT;j++)F[i|j]=min(F[i|j],F[j]+V[i]); } if(F[TOT]==1e9)puts("Impossible"); else cout<<F[TOT]; }

做法二:網路流

建圖比較簡單,從源點向每個A[i]>0的點連邊,容量為A[i],費用為0,從每個A[i]<0的點向匯點連邊,容量為A[i],費用為0,然後點之間的邊容量設為無窮。需要注意的是與普通的費用流不同,每次找到一條路時,要將有流經過的邊費用改成0,將沒有流經過的邊費用改成原值,雙向邊都要改,因為第二次走是不產生費用的。最後再將有流的邊的費用加起來。

程式碼:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<deque>
#define N 123
#define M 1234
using namespace std;
int qg=0,n,m,S,T,A[123];
int TOT=1,LA[N],EN[M],NE[M],G[M],W[M],F[M];
int dis[N],use[N],pre[N],maxflow,mincost;
bool mark[N];
queue<int>Q;
void ADD(int x,int y,int w,int c)
{
    TOT++;
    EN[TOT]=y;
    G[TOT]=w;
    F[TOT]=c;
    W[TOT]=c;
    NE[TOT]=LA[x];
    LA[x]=TOT;
}
bool FP()
{
    int i,x,y;
    memset(dis,60,sizeof(dis));
    dis[S]=0;mark[S]=1;Q.push(S);
    while(!Q.empty())
    {
        x=Q.front();
        Q.pop();
        mark[x]=0;
        for(i=LA[x];i;i=NE[i])
        {
            y=EN[i];
            if(G[i]&&dis[y]>dis[x]+W[i])
            {
                dis[y]=dis[x]+W[i];
                pre[y]=x;use[y]=i;
                if(!mark[y])mark[y]=1,Q.push(y);
            }
        }
    }
    if(dis[T]!=dis[0])return 1;
    return 0;
}
void AF()
{
    int f=1e9,i;
    for(i=T;i!=S;i=pre[i])f=min(f,G[use[i]]);
    maxflow+=f;
    for(i=T;i!=S;i=pre[i])
    {
        G[use[i]]-=f;
        G[use[i]^1]+=f;
        if(G[use[i]]!=1e9)W[use[i]^1]=W[use[i]]=0;
        else
        {
            W[use[i]]=F[use[i]];
            W[use[i]^1]=F[use[i]^1];
        }
    }
}
int main()
{
    int i,x,y,z;
    scanf("%d%d",&n,&m);
    S=n+1;T=S+1;
    for(i=1;i<=n;i++)
    {
        scanf("%d",&A[i]);
        if(A[i]>0)
        {
            ADD(S,i,A[i],0);
            ADD(i,S,0,0);
            qg+=A[i];
        }
        else 
        {
            ADD(i,T,-A[i],0);
            ADD(T,i,0,0);
        }
    }
    for(i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        x++;y++;
        ADD(x,y,1e9,z);
        ADD(y,x,1e9,z);
    }
    while(FP())AF();
    if(maxflow!=qg)puts("Impossible");
    else
    {
        for(i=2;i<=TOT;i++)
        if(G[i]&&G[i]!=1e9)mincost+=F[i];
        cout<<mincost/2;
    }
}