题解 洛谷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
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值