CF311E Biologist

link

最小割

双倍经验!!

三倍经验!!!

这道题又是一道的经典的最小割模型:集合划分

思路和建图大致都和我放的经验一样:先将所有利润加起来,再减去最小代价(即最小割),便是最大利润了。而对于每个要求新建两个节点连边即可。详细可见这道题的思路和建图,这里顺便安利一波我写的题解qwq。

但是这道题不同的地方就在于对于每个位置,改变数字有代价;对于每个要求,无法满足也有代价。但是我们的最小割算的是收益。

其实很简单,我们发现在最小割中,格割掉一条边,也就是付出这条边容量的代价。所以我们只需根据题意将一些边的容量设为这个代价即可(具体哪些边建图里面讲)。

建图

以下每个位置在最小割后,若与 \(s\) 联通,则表明该位置最终为 \(0\),若与 \(t\) 联通,则表明该位置最终为 \(1\)

源点连每个位置,每个位置连汇点,容量均为 \(0\)。如果该位置的初始数为 \(1\),则将该位置与 \(s\) 连的边容量改为修改代价;反之若为 \(0\),则将该位置与 \(t\) 连的边容量改为修改代价。(这里有点绕:如果初始为 \(0\),那么以后想要将他改为 \(1\),也就意味着在最小割中,让这个点从 \(s\) 子集转到 \(t\) 子集,那么就需要割掉这条与 \(s\) 相连的边,并付出代价)。

对于每一个要求 \(i\),新建两个点 \(x_i,y_i\)

\(s\)\(x_i\) 容量为 \(0\)\(y_i\)\(t\) 容量为 \(0\)。然后要求是什么就把利润给哪条边(\(0\) 就将连 \(s\) 边容量改为利润,\(1\) 就改连 \(t\) 的边)

对于该要求的每个位置 \(j\in i\)

\(x_i\)\(j\) 容量为 \(+\infty\)\(j\)\(y_i\) 容量为 \(+\infty\)。连 \(+\infty\) 是为了保证该要求内的每个位置的数值统一。

如果完成不了有代价,那么就将容量为利润的边的容量再加上 \(g\),但是这个 \(g\) 不用加入到利润之和中。如果无法完成要求,自然就会割掉这条代表利润的边,表示放弃这份利润,同时也付出了代价,最终利润也就减掉了这份利润 \(+\) \(g\)

最后答案为 \(\sum_{i=1}^mW_i-Mincut\)

Code

#include<bits/stdc++.h>
//#define int long long
#define pair pair<int,int>
using namespace std;
inline void end()
{
	puts("");
	system("pause");
}
inline int read()
{
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
	while (c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*f;
}
const int N=3e4+4,M=5e5+5;
int n,m,g,s,t,ans,Maxflow;
int first[N],nex[M],to[M],w[M],num=1;
inline void add(int u,int v,int val)
{
	nex[++num]=first[u];
	first[u]=num;
	to[num]=v;
	w[num]=val;
}
inline void Add(int u,int v,int val)
{
	add(u,v,val);
	add(v,u,0);
}
namespace ISAP
{
	int dep[N],gap[N],cur[N];
	void bfs()
	{
		memset(dep,-1,sizeof(dep));
		memset(gap,0,sizeof(gap));
		queue<int> q;
		q.push(t);
		dep[t]=0;gap[0]=1;
		while(!q.empty())
		{
			int u=q.front();q.pop();
			for(int i=first[u];i;i=nex[i])
			{
				int v=to[i];
				if(dep[v]!=-1) continue;
				dep[v]=dep[u]+1;
				gap[dep[v]]++;
				q.push(v);
			}
		}
	}
	inline int dfs(int u,int in)
	{
		if(u==t) return in;
		int out=0;
		for(int i=cur[u];i;i=nex[i])
		{
			cur[u]=i;
			int v=to[i];
			if(!w[i]||dep[v]!=dep[u]-1) continue;
			int res=dfs(v,min(w[i],in-out));
			w[i]-=res;
			w[i^1]+=res;
			out+=res;
			if(in==out) return out;
		}
		gap[dep[u]]--;
		if(!gap[dep[u]]) dep[s]=n+m+m+3;
		dep[u]++;
		gap[dep[u]]++;
		return out;
	}
	void work()
	{
		bfs();
		while(dep[s]<n+m+m+2)
		{
			memcpy(cur,first,sizeof(first));
			Maxflow+=dfs(s,1e9);
		}
	}
}
int kind[N];
int main()
{//s->0,t->1
	n=read(),m=read(),g=read();
	s=0,t=n+m+m+1;//n个位置+2m个因要求新建的点
	for(int i=1;i<=n;++i) kind[i]=read();
	for(int i=1;i<=n;++i)
	{
		int x=read();//这里注意别建反了
		if(kind[i]) Add(i+m,t,x),Add(s,i+m,0);
		else Add(i+m,t,0),Add(s,i+m,x);
	}
	for(int i=1;i<=m;++i)
	{
		int kk=read(),val=read(),siz=read();ans+=val;//只加利润,别加代价
		if(kk) Add(i+n+m,t,val),Add(s,i,0);
		else Add(s,i,val),Add(i+n+m,t,0);
		while(siz--)
		{
			int x=read();
			Add(i,x+m,1e9);
			Add(x+m,i+m+n,1e9);
		}
		int kkk=read();
		if(kkk)
		{
			if(kk) Add(i+n+m,t,g);
			else Add(s,i,g);
		}
	}
	ISAP::work();
	printf("%d",ans-Maxflow);
	end();
	return 0;
}
posted @ 2021-07-07 20:16  After-glow  阅读(68)  评论(0)    收藏  举报