【CF802O】April Fools' Problem (hard)

题目

题目链接:https://codeforces.com/contest/802/problem/O
\(n\) 道题, 第 \(i\) 天可以花费 \(a_i\) 准备一道题, 花费 \(b_i\) 打印一道题, 每天最多准备一道, 最多打印一道, 准备的题可以留到以后打印, 求最少花费使得准备并打印 \(k\) 道题。
\(k,n\leq 500000,1\leq a_i,b_i\leq 10^9\)

思路

考虑本题的弱化版 CF802N,也就是 \(n,k\leq 2200\)。直接上费用流随便搞搞就好了。
费用流有一个性质:每次增广时的费用一定不小于上一次增广的费用。如果记 \(f_i\) 表示增广 \(i\) 次的费用之和,那么 \(f_i-f_{i-1}\) 就是单调不减的。所以如果把所有 \((i,f_i)\) 扔到一个二维平面上,就一定会形成一个下凸壳。
由于本题要求恰好打印 \(k\) 题,考虑 wqs 二分。对于每一组匹配 \((a_i,b_j)\) 都减去 \(mid\) 的贡献,一个 naive 的想法是直接从前往后枚举贪心匹配,根据最后匹配的数量和 \(k\) 比较来更新二分的区间。
但是直接贪心显然是错误的。假设此时枚举到 \(b_k\),可能之前有一组匹配 \((a_i,b_j)\) 改为 \((a_i,b_k)\) 更优。这样的话造成的贡献就是 \(b_k-b_j\)。这启发我们采用反悔贪心。
维护一个小根堆,从小到大枚举 \(i\),把 \(a_i-mid\) 插入小根堆中。然后考虑当前的 \(b_i\) 与堆顶匹配,如果 \(b_i+\) 堆顶元素 \(<0\),那么就匹配上这一组,并把 \(-b_i\) 扔进堆中,方便以后反悔。
\(cnt\) 计数的话就在匹配的过程中统计一下匹配了多少个 \(a_i\) 即可。
时间复杂度 \(O(n\log n\log V)\),其中 \(V\) 是值域。

代码

#include <bits/stdc++.h>
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef long long ll;

const int N=500010;
int n,m,cnt;
ll ans,sum,a[N],b[N];
priority_queue<pair<ll,bool> > q;

int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++) scanf("%I64d",&a[i]);
	for (int i=1;i<=n;i++) scanf("%I64d",&b[i]);
	ll l=0,r=2e9,mid;
	while (l<=r)
	{
		while (q.size()) q.pop();
		mid=(l+r)>>1; sum=cnt=0;
		for (int i=1;i<=n;i++)
		{
			q.push(mp(mid-a[i],0));
			if (b[i]-q.top().fi<0)
			{
				if (!q.top().se) cnt++;
				sum+=b[i]-q.top().fi;
				q.pop(); q.push(mp(b[i],1));
			}
		}
		if (cnt>=m) ans=sum+mid*m,r=mid-1;
			else l=mid+1;
	}
	cout<<ans;
	return 0;
}
posted @ 2021-07-14 07:38  stoorz  阅读(82)  评论(0编辑  收藏  举报