【题解】 闷声刷大题 带悔贪心+wqs二分

Legend

大神犇 YCY 现在有 \(k\) 道神题要刷(假定神题没有区别),刷一道题要分两个步骤,必须要先想出正解再写出正解。

他一共有 \(n\) 天时间可以刷题,但他每天最多只能想出一道题和写出一道题,可以在同一天想出和写出同一道题。

定义刷一道题的代价为想的代价 \(a_i\) + 写的代价$ b_i$,YCY 想最小化 \(k\) 道题的代价和。

\(n \le 150\,000\),代价都是正数。

Editorial

首先这个题有个很显然的费用流搞法,显然就 TLE 了。

其次有个 \(O(n^3)\) 的 dp,设 \(dp_{i,j,k}\) 表示考虑第 \(i\) 想题和第 \(j\) 天做题匹配,已经做完了 \(k\) 道题的最小代价。

转移则按是否 \(i,j\) 配对来转移,不配对则 \(i \gets i+1\)\(j \gets j+1\),否则两个同时 \(+1\)

这个 dp 可以被优化到 \(O(n^2\log (a+b))\),第三维可以通过 \(wqs\) 二分搞掉。

为什么是凸的?

你可以看成一个二分图的带权匹配,这显然是个下凸函数。考虑它如果不下凸,则我们可以通过构造更优解来进行反证。

现在复杂度的瓶颈在于 \(O(n^2)\) 的 dp,它实在是太慢了,有没有什么其它搞法?

答案是有的,就是带悔贪心。

考虑维护一个待选题目集合 \(S\),我们从左到右加入“想题目”,同时每天考虑是否“做题目”。

如果做题目 + 想题目的代价 \(\le 0\),我们就考虑形成这个配对,否则不形成。

但是之后出现了更优的“做题目”,怎么办?我们要把当前的“想”配对的“做”撤回,即往 \(S\) 中加入负的“做”的代价。

复杂度 \(O(n\log n \log (a+b))\)

Code

特别地,根据二分的实现方法,你需要为“想”和撤回“做”两种不同的贪心方式定义先后顺序,

因为先后顺序的不同会导致 wqs 二分到的位置不一。

#include <bits/stdc++.h>

#define debug(...) fprintf(stderr ,__VA_ARGS__)
#define __FILE(x)\
	freopen(#x".in" ,"r" ,stdin);\
	freopen(#x".out" ,"w" ,stdout)
#define LL long long

const int MX = 150000 + 23;

using namespace std;

int read(){
	char k = getchar(); int x = 0;
	while(k < '0' || k > '9') k = getchar();
	while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
	return x;
}

int n ,k;
int a[MX] ,b[MX];
LL ans;

struct GOOD{
	int type;
	LL x;
	bool operator <(const GOOD& B)const{
		return x == B.x ? type > B.type : x > B.x;
	}
};

int check(LL mid){
	int choose = 0;
	ans = 0;
	priority_queue<GOOD> q;
	for(int i = 1 ; i <= n ; ++i){
		q.push((GOOD){1 ,a[i] - mid});
		if(!q.empty() && q.top().x + b[i] <= 0){
			if(q.top().type == 1) ++choose;
			ans += b[i] + q.top().x;
			q.pop();
			q.push((GOOD){2 ,-b[i]});
		}
	}
	return choose;
}

int main(){
	n = read() ,k = read();
	for(int i = 1 ; i <= n ; ++i) a[i] = read();
	for(int i = 1 ; i <= n ; ++i) b[i] = read();
	LL l = 0 ,r = 2e9 ,mid;
	while(l <= r){
		// debug("%d %d\n" ,l ,r);
		int mid = (l + r) / 2;
		if(check(mid) >= k) r = mid - 1;
		else l = mid + 1;	
	}
	check(r + 1);
	printf("%lld\n" ,ans + (r + 1LL) * k);
	return 0;
}
posted @ 2021-02-22 09:25  Imakf  阅读(162)  评论(0编辑  收藏  举报