LG5308 [COCI2019] Quiz(wqs二分+斜率优化DP)


LG5308 [COCI2019] Quiz

作为 wqs 二分的一道入门题,值得写一篇题解。

解题思路

首先我们考虑 \(O(n^2k)\) 的普通 DP。

我们令 \(f_{i,k}\)? 为考虑淘汰 \(i\) 个人,分成 \(k\) 轮淘汰的最大收益。我们可以得到转移方程:

\[f_{i,k}=\max\limits_{j=0}^{i-1} f_{j,k-1}+\frac{i-j}{i} \]

这个式子很难优化。二维的式子一般都很难进行优化。我们考虑化为一维状态,显然不太好化。

我们考虑先不管 \(k\)? 的限制,那么我们有:

\[f_{i}=\max\limits_{j=0}^{i-1} f_{j}+\frac{i-j}{i} \]

这样我们对这个式子进行 DP。(显然不考虑 \(k\)\(f_i=\sum\limits_{i=1}^i \frac 1i\),但是我们先不管这个)

对于 \(k,j\) 两个转移点,若 \(j\) 优于 \(k\),我们有:

\[f_{j}+\frac{i-j}{i}>f_{k}+\frac{i-k}{i} \]

\[\frac{f_j-f_k}{j-k}>\frac 1i \]

显然可以进行斜率优化了。时间复杂度为 \(O(n)\)

我们在记录一个 \(g_i\),记录 \(f_i\) 这个状态分割了几段。显然,若最优转移点为 \(j\)\(g_i=g_j+1\)

现在考虑对于每次分割给予一些“奖赏”(“惩罚”也彳亍)。假设奖赏为 \(x,x\in R\),则转移方程变成了:

\[f_{i}=\max\limits_{j=0}^{i-1} f_{j}+\frac{i-j}{i}+x \]

显然 \(x\) 越小分的段数越少。读者自证不难

而且这个方程还是可以斜率优化,式子和上面那个一样。但是此时我们可以通过控制 \(x\) 来控制分的段数。显然可以二分 \(x\)。每次检验 \(g_n\)\(k\) 的关系即可。

二分出合适的 \(x\) 后把奖赏退回去即可。

时间复杂度 \(O(n\log N)\)

代码实现

//Don't act like a loser.
//This code is written by huayucaiji
//You can only use the code for studying or finding mistakes
//Or,you'll be punished by Sakyamuni!!!
#include
#define lb long double
using namespace std;

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

const int MAXN=1e5+10; 
const lb eps=1e-11;

int n,m,h,t;
int q[MAXN],g[MAXN];
lb f[MAXN];
lb k(int x,int y) {
	return (f[x]-f[y])/(x-y*1.0);
}

int dp(lb x) {
	h=t=1;
	q[1]=0;
	for(int i=1;i<=n;i++) {
		while(heps+1.00/i) {
			h++;
		}
		f[i]=f[q[h]]+(i*1.0-1.0*q[h])/(1.0*i)+x;
		g[i]=g[q[h]]+1;
		while(hm;
}

signed main() {
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);

	cin>>n>>m;
	
	lb l=-1e9,r=1e9,mid=0;
	for(int i=1;i<=150;i++) {
		mid=(l+r)/2;
		if(dp(mid)) {
			r=mid;
		}
		else {
			l=mid;
		}
	}
	
	dp((l+r)/2);
	cout<

相关