P2762 太空飞行计划问题

传送门

经典的最大权闭合子图问题

实验有正的价值,仪器的价值为负

为了实验我们必须选择相应的仪器

所以从 S 连向实验,边权为实验的价值

实验与相应仪器之间连边,边权为 INF

仪器连向 T 边权为仪器的价格

解释:

首先最大权闭合子图就是要求在一个图中求出一个联通子图

该子图没有出边能到达非子图的其他点(闭合)

且权值最大

根据我们刚才的建图方式,S 到正权值的点连边,边权为点权,负权值点连向 T,边权为点权绝对值

点之间根据依赖关系连边,边权INF

如果我们搞一个最小割,考虑最小割的实际意义

显然只会割从 S 到实验的边和从仪器到 T 的边

如果割的是从 S 到实验的边则相当于我们放弃了此实验从而可以不选择此实验需要的仪器,要扣去实验的价值

如果割的是从仪器到 T 的边则向当于我们为了某些实验而选择了此仪器,要扣去仪器的价值

那么最大收益就是实验总价值(包括没进行的实验)减去最小割,显然减最小割是最优的方案,因为如果还有流量那么说明有实验还有仪器没得到

那么还要继续考虑放弃实验或购买仪器

最后求出最大收益还不够,还要具体到实验和仪器

发现数据其实不大

所以枚举仪器,每次把此仪器的价格调成 0,如果跑完最小割发现最终收益恰好多了此仪器的价格则说明此仪器有被选择

然后根据选择的仪器就可以知道哪些实验有被进行

因为读入十分毒瘤,所以要稍微注意一下

我读入用快读加了个特判就解决了

具体看代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
using namespace std;
typedef long long ll;
bool flag;//flag用来特判是否换行
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    if(ch=='\n') flag=1;//如果有换行flag=1
    return x*f;
}
const int N=4e5+7,INF=1e9+7;
int fir[N],from[N<<1],to[N<<1],val[N<<1],cntt,Fir[N];
inline void add(int a,int b,int c)
{
    from[++cntt]=fir[a]; fir[a]=cntt;
    to[cntt]=b; val[cntt]=c;
    from[++cntt]=fir[b]; fir[b]=cntt;
    to[cntt]=a; val[cntt]=0;
}
int n,m,S,T;
int ans;
int dep[N];
queue <int> q;
int BFS()
{
    for(int i=1;i<=T;i++) dep[i]=0;
    q.push(S); dep[S]=1;
    while(!q.empty())
    {
        int x=q.front(); q.pop();
        for(int i=fir[x];i;i=from[i])
        {
            int &v=to[i]; if(dep[v]||!val[i]) continue;
            q.push(v); dep[v]=dep[x]+1;
        }
    }
    for(int i=1;i<=T;i++) Fir[i]=fir[i];
    return dep[T];
}
int dfs(int x,int mif)
{
    if(x==T||!mif) return mif;
    int fl=0,res=0;
    for(int i=Fir[x];i;i=from[i])
    {
        Fir[x]=i; int &v=to[i]; if(dep[v]!=dep[x]+1) continue;
        if( res=dfs(v,min(mif,val[i])) )
        {
            fl+=res; mif-=res;
            val[i]-=res; val[i^1]+=res;
            if(!mif) break;
        }
    }
    return fl;
}
int Val[N],Cst[N];//Val是实验价值,Cst是仪器花费
vector <int> ned[N];//存实验需要的仪器编号
int solve(int P)//枚举仪器求最大收益
{
    memset(fir,0,sizeof(fir)); cntt=1;//记得初始化
    int res=0;
    for(int i=1;i<=n;i++)
    {
        add(S,i,Val[i]);
        res+=Val[i];
        int len=ned[i].size();
        for(int j=0;j<len;j++)
            add(i,n+ned[i][j],INF);
    }
    for(int i=1;i<=m;i++)
        if(i!=P) add(n+i,T,Cst[i]);//建图不解释
    while(BFS()) res-=dfs(S,INF);
    return res;//返回最大收益
}
bool vis[N];//判断仪器是否购买
int main()
{
    int a;
    n=read(),m=read();
    S=n+m+1; T=n+m+2;
    for(int i=1;i<=n;i++)
    {
        flag=0; Val[i]=read();
        while(!flag)//判断是否换行
            ned[i].push_back(read());
    }
    for(int i=1;i<=m;i++) Cst[i]=read();
    ans=solve(0);//先求一波最大收益
    for(int i=1;i<=m;i++)//枚举仪器
    {
        int p=solve(i);
        if(p-ans==Cst[i]) vis[i]=1;//注意要恰好等于
    }
    for(int i=1;i<=n;i++)//然后根据仪器求出实验是否进行
    {
        int P=1,len=ned[i].size();
        for(int j=0;j<len;j++) P&=vis[ned[i][j]];
        if(P) printf("%d ",i);
    }
    printf("\n");
    for(int i=1;i<=m;i++) if(vis[i]) printf("%d ",i);
    printf("\n");
    printf("%d",ans);
    return 0;
}

 

posted @ 2018-12-28 16:17  LLTYYC  阅读(172)  评论(0编辑  收藏  举报