题解 洛谷P1361 小M的作物
Date 2019.8.9
题目大意
有两块容量为无限大的耕地A,B。对于每一种种子,种在A里的收益为ai,种在B里的收益为bi。特别地,对于m种组合,如果组合中的种子全部种在A里,可以获得c1的额外收益,全部种在B里则可以获得c2的额外收益。求最大的收益。
思路
我们考虑对于一种种子要不在A地,要不在B地。当我们取其中一种情况时,就要把另一种舍去。如果在图上有一条边连接当前的种子和A,我们就要把将它和B连起来的边断开。
这不就是最小割吗?!
但怎么建图呢?
显然我们可以取A为源点,取B为汇点。对于任意一点,我们连一条从A到当前点的边,容量为ai,再连一条从当前点到B的边,容量为bi。
以上是最普通的情况,那么对于那m种组合,我们应该怎么去处理呢?
假设现在有一种组合对应着一个点集{u,v},只要u和v中的任意一个不在A中,我们就无法得到c1的额外收益。我们肯定不可以分别从A连向u,v,因为这个样子即使到v的边断了,到u的边依然有可能会使c1的额外收益流到汇点,但这并不合题意。
也就是说,我们需要有一条边满足只要这条边被割断,那么c1的额外收益就肯定不会流到汇点。
那么这条边应该由A连向谁呢?显然不可能是图中原有的点了,因为不管是c1还是c2,都是这整个组合的额外收益,所以c1要能流入这个点集中的任意一个点。
那么解决方案就十分明显了——建一个虚点x,从A连一条边到x,边的容量为c1;再从x连向点集中的每一个点,边的容量为无穷大,即无法被割断。
对于点集里的点到B中的处理与以上相同。
根据最大流=最小割,我们只要在建好的图上跑一遍最大流,并用所有的值减去求出的最小割(最少的取不到的价值),就能求出答案了。
注意
这道题加了许多虚点和虚边,所以数组大小至少要为1e7
下面附上我的代码
#include<bits/stdc++.h>
#define maxn 1000009
#define inf 0x7fffffff
using namespace std;
int cnt,to[maxn*2],val[maxn*2],Next[maxn*2],head[maxn],cur[maxn*2];
int n,m,s,t,a[maxn],b[maxn],k,c1,c2,d[maxn],sum;
long long ans,all;
void Add (int x,int y,int z)
{
cnt++;
to[cnt]=y;
Next[cnt]=head[x];
head[x]=cnt;
val[cnt]=z;
}
void Read ()
{
scanf("%d",&n);
s=n+1,t=n+2;
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
Add(s,i,a[i]),Add(i,s,0);
all+=a[i];
}
for (int i=1;i<=n;i++)
{
scanf("%d",&b[i]);
all+=b[i];//all用来统计所有答案
Add(i,t,b[i]),Add(t,i,0);
}
scanf("%d",&m);
sum=0;
for (int i=1;i<=m;i++)
{
sum++;
scanf("%d%d%d",&k,&c1,&c2);
Add(s,n+2+sum,c1),Add(n+2+sum,s,0);//连接A和点集的虚点
all+=c1+c2;
for (int i2=1;i2<=k;i2++)
{
int x;
scanf("%d",&x);
Add(n+2+sum,x,inf),Add(x,n+2+sum,0);
Add(x,n+2+sum+1,inf),Add(n+2+sum+1,x,0);
}
Add(n+sum+2+1,t,c2),Add(t,n+sum+2+1,0);//连接点集和B的虚点
sum++;
}
}
bool Bfs ()
{
memset(d,-1,sizeof(d));
queue <int> q;
while (!q.empty())
q.pop();
q.push(s);
d[s]=0;
for (int i=1;i<=n+2+sum;i++)
cur[i]=head[i];
while (!q.empty())
{
int x=q.front();
q.pop();
for (int i=head[x];i!=-1;i=Next[i])
{
int o=to[i];
if (d[o]==-1&&val[i])
{
d[o]=d[x]+1;
q.push(o);
}
}
}
if (d[t]!=-1)
return true;
else
return false;
}
int Dfs (int x,int low)
{
if (x==t||low==0)
return low;
int totflow=0;
for (int i=cur[x];i!=-1;i=Next[i])
{
cur[x]=i;
int o=to[i];
if (d[o]==d[x]+1&&val[i])
{
int a=Dfs(o,min(low,val[i]));
val[i]-=a;
val[i^1]+=a;
low-=a;
totflow+=a;
if (low==0)
return totflow;
}
}
if (low!=0)
d[x]=-1;
return totflow;
}
void Dinic ()
{
while (Bfs())
ans+=Dfs(s,inf);
}
int main ()
{
cnt=-1;
memset(head,-1,sizeof(head));
Read();
Dinic();//跑一遍dinic
cout<<all-ans<<endl;
return 0;
}
尾记
刚学dinic,急着把板子背着写一遍,结果最后忘了要用总答案减去最小割,白调了半天TAT