用最小割做最大流的神仙思路

%%% 神仙 tzc 爆切 arcE!

一眼 wll:源点连向所有零食 \(i\),容量为 \(a_i\);所有零食 \(i\) 连向所有小孩 \(j\),容量为 \(b_j\);所有小孩 \(j\) 连向汇点,容量为 \(c_j\)。跑最大流。但是这个图的边数是 1e10,存都存不下,就不会做了。

然后就是非常神仙的思路了 %%%%%:将最大流转化成最小割(u1s1 用最大流做最小割很正常,但是用最小割做最大流还真的没见过)。我们知道最小割要比最大流显式得多,所以在特殊构造的图里面最小割往往更好求。

先来扯这么一件事:很多人以为割的定义是一个边集使得割掉之后 \(s\to t\)​​​ 不连通,其实不然,这玩意叫做「割边集」;割的定义其实是对点集的一个划分 \(S,T\)​​​ 使 \(s\in S,t\in T\)​​​,\(S\to T\)​​ 所有边的集合为一个割。但是很好的事情是:在边权都非负的图上,最小割等于最小割边集,这样我们就可以求更直观的最小割边集 instead of 定义比较棘手的最小割了。证明:显然所有割都是割边集,所以最小割 ≥ 最小割边集;对每个割边集,从 \(s\)​ 开始 dfs 显然到不了 \(t\)​,令所有到的节点集合为 \(S\)​,考虑割 \((S,V-S)\)​,这显然是原割边集去掉一些边,由于边权是正的,所以边权和不升,得到最小割 ≤ 最小割边集。得证。

有了这个思路就不算难了。设 \(a\)​ 边割掉的序列为 \(x\)​,\(c\)​ 边割掉的序列为 \(y\)​,此时最小割边集显然为 \(\sum\limits_{i=1}^{|x|}{a_{x_i}}+\sum\limits_{i=1}^{m}\begin{cases}c_i&i\in y\\(n-|x|)b_i&i\notin y\end{cases}\)​。注意到右边的 sum 跟 \(x\)​ 具体是多少无关,只跟 \(|x|\)​ 有关,所以在 \(|x|\)​ 确定的情况下应该选尽可能小的 \(a_{x_i}\)​,那就排个序前缀和。考虑枚举 \(|x|\)​,右边那项最优决策显然是 \(\sum\limits_{i=1}^m\min(c_i,(n-|x|)b_i)\)​。考虑随 \(|x|\)​ 变化维护这玩意,显然对每个 \(i\)​ 决策是单调的,即存在分界点使得一边全选 \(c_i\)​ 一边全选 \((n-|x|)b_i\)​。那就预处理出所有分界点(除法上取整即可)存到对应位置 todo-list 里,随便搞就行了。不算排序,复杂度线性。

code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define pb push_back
const int inf=0x3f3f3f3f3f3f3f3f;
const int N=200010;
int n,m;
int a[N],b[N],c[N];
vector<int> chg[N];
signed main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)scanf("%lld",a+i);
	for(int i=1;i<=m;i++)scanf("%lld",b+i);
	for(int i=1;i<=m;i++)scanf("%lld",c+i);
	sort(a+1,a+n+1);
	for(int i=1;i<=n;i++)a[i]+=a[i-1];
	int ans=inf,sum=0,now=0;
	for(int i=1;i<=m;i++){
		now+=b[i];
		int x=(c[i]+b[i]-1)/b[i];
		if(x>n)continue;
		chg[n-x].pb(i);
	}
	for(int i=n;~i;i--){
		ans=min(ans,a[i]+sum);
		if(i){
			for(int j=0;j<chg[i-1].size();j++){
				int x=chg[i-1][j];
				now-=b[x];
				sum+=-(n-i)*b[x]+c[x];
			}
			sum+=now;
		}
	}
	cout<<ans;
	return 0;
}
珍爱生命,远离抄袭!