题解 洛谷P1361 小M的作物

题解 洛谷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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值