做法
每天可以用 的时间准备一道题和用 的时间打印一道已经准备过的题,求打满 道题的最小花费。
注意到随着 增加,答案及斜率单调增,于是可以用 贰分。
贰分每一个题需要的额外贡献,在每一个 加上他,再用 去匹配前面的某一个 ,用反悔贪心即可实现。
具体的,维护一个小根堆,从 到 依次枚举一遍,先加入 ,再用 去匹配最小的 ,然后在堆中加入 表示反悔,但是要注意当 匹配的值是正数的话是不可以进行匹配的,否则最后的 计数的并不是最优的,还可以减去这些正数的贡献。
复杂度是 。
线段树优化网络流做法
有一个显然的建图方式。
- 向 连一条
- 向 连一条
- 向 连一条
- 向 连一条
我们将一个合法的匹配方式中的 视为 , 视为 ,这个匹配一定是合法的括号匹配。
考虑反悔机制。
插入一个 ,那么 的前缀和数组 ,插入一个 , 减一。
可以维护以下东西:
- 
:区间前缀和最小值 
- 
: 的最小值的 
- 
: 的最小值的 
- 
:满足区间前缀和最小值大于 的值最小的 
然后就是一大堆东西,Immaculacy 博客。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=4e6;
int n,k,a[N],b[N];
struct node
{
	int val,f;
	
	friend bool operator<(node a,node b)
	{
		return a.val>b.val;
	}
};priority_queue<node>q;
main()
{
	cin>>n>>k;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=n;i++)cin>>b[i];
	int l=0,r=2e9;
	while(l<=r)
	{
		while(!q.empty())q.pop();
		int mid=(l+r)>>1,res=0,num=0;
		for(int i=1;i<=n;i++)
		{
			int val=a[i]-mid;
			q.push((node){val,1});
			int p=b[i]+q.top().val;
			if(p<=0)
			{
				num+=q.top().f;res+=p;
				q.pop();q.push((node){-b[i],0});
			}
		}
		if(num==k)
		{
			cout<<res+k*mid;
			return 0;
		}
		if(num<k)l=mid+1;
		if(num>k) r=mid-1;
	}
}
 
    