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

做法一:最小生成树+状压dp

枚举点集,若当前点集构成联通块且能量之和为0,显然当前联通块传递能量的最小代价是其最小生成树,因此将每个这样的联通块看成一个物品,背包dp算出最小费用即可。

代码:

#include
#include
#include
#include
using namespace std;
struct node{int x,y,z;};
bool cmp(node a,node b)
{return a.zint 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<1)&s)tot++,mark[i]=1;
    for(i=1;i<=n;i++)fa[i]=i;
    while(k<=m&&cntif(mark[x]&&mark[y]&&fx!=fy)
        {
            ans+=P[k].z;
            fa[fx]=fy;
            cnt++;
        }
        k++;
    }
    if(cnt+1return 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<1;
    for(i=1;i<=TOT;i++)
    for(j=1;j<=n;j++)if((1<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<

做法二:网络流

建图比较简单,从源点向每个 A[i]>0 的点连边,容量为 A[i] ,费用为0,从每个 A[i]<0 的点向汇点连边,容量为 A[i] ,费用为0,然后点之间的边容量设为无穷。需要注意的是与普通的费用流不同,每次找到一条路时,要将有流经过的边费用改成0,将没有流经过的边费用改成原值,双向边都要改,因为第二次走是不产生费用的。最后再将有流的边的费用加起来。

代码:

#include
#include
#include
#include
#include
#include
#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<2;
    }
}

你可能感兴趣的:(网络流,最小生成树,状态压缩,动态规划)