其他分享
首页 > 其他分享> > 「NOI2016」国王饮水记(猜结论+动态规划+斜率优化)

「NOI2016」国王饮水记(猜结论+动态规划+斜率优化)

作者:互联网

https://loj.ac/problem/2087

干脆把这年NOI改成猜结论大赛好了。

先把\(h\)从小到大排序。

上来先猜三个显然的结论:
1.一个\(h[i]\)不会被用多次
2.我们用到的一定是\(h\)的一个后缀
3.\(h\)的后缀用法,把\(h\)的后缀划分成若干段,从左往右每次合并一段

设\(f[i][j]\)表示合并了\(i\)次,用了\(j\)以前的最大答案。

设\(s\)表示\(h\)的前缀和,则转移:
$ f[i][j]=max(\frac{f[i-1][k]-s[k]+s[i]}{i-k+1})$

暴力就有60分了。

考虑这个相当于二维平面上若干点,要求一个点P和这些点连线的最大斜率。

4.看起来有决策单调性,所以可以用分治优化转移,应该有70分了。

5.答案点一定在凸包上,对于这题,答案点一定在上凸壳,可以在上面三分(二分)。

结合,4,5,可以得到单调队列维护凸壳的做法,复杂度:\(O(nkp)\),可以获得85分,用double做后面的,可以获得91分。

接着开始非人类:
由\(h\)互不相同性质:
6.前一段长度一定大于等于后一段的长度。
7.长度大于1的区间只有\(O(log \frac{nh}{H})\),*这个我已经感受不到正确性了。

于是只做14次dp,剩下的一次扫一遍求最优。

中间可以用double算来卡常数。

Code(除掉高精度模板):

#include<bits/stdc++.h>
#define fo(i, x, y) for(int i = x, _b = y; i <= _b; i ++)
#define ff(i, x, y) for(int i = x, _b = y; i <  _b; i ++)
#define fd(i, x, y) for(int i = x, _b = y; i >= _b; i --)
#define ll long long
#define pp printf
#define hh pp("\n")
using namespace std;

#define db Decimal

const int N = 8005;

int n, k, p;
int h[N];

db f[N], g[N], s[N];

struct P {
	int x; double y;
	P(){}
	P(int _x, double _y) {
		x = _x, y = _y;
	}
};

P operator - (P a, P b) {
	return P(a.x - b.x, a.y - b.y);
}

db operator ^ (P a, P b) {
	return a.x * b.y - a.y * b.x;
}

P z[N]; int L, R;

void add(P a) {
	while(L < R && ((a - z[R]) ^ (z[R] - z[R - 1])) >= 0) R --;
	z[++ R] = a;
}
db qry(P a, P b) { return (b.y - a.y) / (b.x - a.x);}
int qry(P a) {
	while(L < R && qry(z[L + 1], a) > qry(z[L], a)) L ++;
	return z[L].x + 1;
}

int main() {
	freopen("a.in", "r", stdin);
	scanf("%d %d %d", &n, &k, &p);
	fo(i, 1, n) scanf("%d", &h[i]);
	sort(h + 2, h + n + 1);
	if(k >= n) {
		db ans = db(h[1]);
		fo(i, 2, n) {
			if((db) h[i] > ans)
				ans = (ans + h[i]) / 2;
		}
		cout << ans.to_string(p) << "\n";
		return 0;
	}
	fo(i, 2, n) s[i] = s[i - 1] + db(h[i]);
	fo(i, 1, n) f[i] = db(h[1]);
	fo(t, 1, min(14, k)) {
		L = 1, R = 0;
		fo(i, 1, n) {
			g[i] = f[i];
			add(P(i - 1, (s[i] - f[i]).to_double()));
			int x = qry(P(i, s[i].to_double()));
			f[i] = max(f[i], (g[x] + s[i] - s[x]) / (i - x + 1));
		}
	}
	db ans = db(0);
	if(k <= 14) {
		fo(i, 2, n) ans = max(ans, f[i]);
	} else {
		int st = max(1, n - (k - 14));
		ans = db(f[st]);
		fo(i, st + 1, n) {
			ans = (ans + h[i]) / 2;
			ans = max(ans, f[i]);
		}
	}
	cout << ans.to_string(p) << "\n";
}

标签:int,double,db,斜率,NOI2016,ans,qry,饮水,define
来源: https://www.cnblogs.com/coldchair/p/12803643.html