【CF802O】April Fools' Problem (hard)

题目

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

思路

考虑本题的弱化版 CF802N,也就是 (n,kleq 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(nlog nlog 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;
}
原文地址:https://www.cnblogs.com/stoorz/p/15009190.html