網路流專題 太空飛行計劃(最大權閉合子圖)
題目描述
W 教授正在為國家航天中心計劃一系列的太空飛行。每次太空飛行可進行一系列商業性實驗而獲取利潤。現已確定了一個可供選擇的實驗集合E={E1,E2,…,Em},和進行這些實驗需要使用的全部儀器的集合I={I1,I2,…In}。實驗Ej需要用到的儀器是I的子集RjÍI。配置儀器Ik的費用為ck美元。實驗Ej的贊助商已同意為該實驗結果支付pj美元。W教授的任務是找出一個有效演算法,確定在一次太空飛行中要進行哪些實驗並因此而配置哪些儀器才能使太空飛行的淨收益最大。這裡淨收益是指進行實驗所獲得的全部收入與配置儀器的全部費用的差額。
對於給定的實驗和儀器配置情況,程式設計找出淨收益最大的試驗計劃。
輸入輸出格式
輸入格式:
第1行有2 個正整數m和n。m是實驗數,n是儀器數。接下來的m 行,每行是一個實驗的有關資料。第一個數贊助商同意支付該實驗的費用;接著是該實驗需要用到的若干儀器的編號。最後一行的n個數是配置每個儀器的費用。
輸出格式:
第1 行是實驗編號;第2行是儀器編號;最後一行是淨收益。
輸入輸出樣例
輸入樣例#1: 複製
2 3
10 1 2
25 2 3
5 6 7
輸出樣例#1: 複製
1 2
1 2 3
17
說明
感謝@FlierKing 提供spj
n,m<=50
這道題資料是在windows生成的,輸入資料中所有的換行都是'\r\n'而不是'\n'
讀入某實驗需要用到的儀器編號的時候,可以這麼讀入。(感謝@zhouyonglong的提供)
題目大意:給出一些實驗,做這些實驗有一個收益,同時需要買一些儀器每個儀器有一個花費。求最大收益並且輸出方案。
解題思路:每個實驗如果選的話就要買對應的儀器,一起可以供另一些實驗使用。儀器與實驗存在依賴關係。
轉化為網路流模型:如果把每個實驗與儀器之間連一條邊的話,如果選則該實驗則該實驗所連的邊對應的點也要選,這明顯符合閉合子圖的定義。
關於建圖:建立一個源點和匯點,每個實驗與原點連一條邊,權值為實驗的收益,實驗與儀器之間連一條容量為inf的邊,儀器與
匯點連一條容量為花費的邊。然後這個題就是讓求一個最大權閉合子圖。
一個定理:最大權閉合子圖的權值等於所有正權點之和減去最小割。
根據我們建的圖可知,最小割把原來的二分圖劃分成兩個集合S,T,割邊的時候一定不會割掉實驗與儀器之間的邊。
總的來說就是:最小割也對應了一個閉合子圖,這個子圖就是S集合去點源點,並且這個子圖的權值最大。
這個題讀入有點坑,我借鑑了一下別人是怎麼讀入的。
這個我用的是EK求最大流並且重新bfs了一下源點能到達的點(這些就是S),如果用dinic去做的話,最後一次分層有標號的點就是S集合。
#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<set>
#include<algorithm>
#include<cstring>
using namespace std;
#define rep(i,j,k) for(int i=j;i<=k;i++)
#define sca(x) scanf("%d",&x)
#define per(i,j,k) for(int i=j;i>=k;i--)
#define inf 0x3f3f3f3f
#define LL long long
#define N 40005
#define inf 0x3f3f3f3f
const LL mod = 998244353;
int flag;
int n,m;
struct edg
{
int to,w,nt;
}g[N];
struct node
{
int id,v;
}pre[N];
int vis[N],head[N];
bool bfs(int s,int t)
{
memset(vis,false,sizeof(vis));
queue<int>q;
vis[s]=0;
q.push(s);
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=head[u];i!=-1;i=g[i].nt)
{
int to=g[i].to;
if(!vis[to]&&g[i].w>0)
{
vis[to]=true;
pre[to].v=u;
pre[to].id=i;
if(to==t)return true;
q.push(to);
}
}
}
return false;
}
int EK(int s,int t)
{
int ans=0;
while(bfs(s,t))
{
int mini=inf;
for(int i=t;i!=s;i=pre[i].v)
{
mini=min(mini,g[pre[i].id].w);
}
for(int i=t;i!=s;i=pre[i].v)
{
g[pre[i].id].w-=mini;
g[pre[i].id^1].w+=mini;
}
ans+=mini;
}
return ans;
}
int tot;
void addedg(int u,int v,int w)
{
g[tot].to=v;
g[tot].w=w;
g[tot].nt=head[u];
head[u]=tot++;
}
void get_set(int s,int t)
{
int Set[205];
memset(vis,false,sizeof(vis));
memset(Set,0,sizeof(Set));
queue<int>q;
vis[s]=true;
q.push(s);
while(!q.empty())
{
int u=q.front();
q.pop();
Set[u]=1;
for(int i=head[u];i!=-1;i=g[i].nt)
{
int to=g[i].to;
if(!vis[to]&&g[i].w>0)
{
vis[to]=1;
q.push(to);
}
}
}
for(int i=1;i<=n;i++)
{
if(Set[i])printf("%d ",i);
}
printf("\n");
for(int i=n+1;i<=110;i++)
{
if(Set[i]&&i!=t)printf("%d ",i-n);
}
printf("\n");
}
int main()
{
scanf("%d%d",&n,&m);
int sum=0;
memset(head,-1,sizeof(head));
rep(i,1,n)
{
int tmp;
scanf("%d",&tmp),sum+=tmp;
addedg(0,i,tmp);
addedg(i,0,0);
char c;
while(1)
{
scanf("%d%c",&tmp,&c);
addedg(i,n+tmp,inf);
addedg(n+tmp,i,0);
if(c=='\n'||c=='\r')break;
}
}
rep(i,1,m)
{
int tmp;
sca(tmp);
addedg(n+i,m+n+1,tmp);
addedg(n+m+1,n+i,0);
}
int ans=EK(0,n+m+1);
get_set(0,n+m+1);
printf("%d\n",sum-ans);
}