出题人题解 | #小y的纸牌#

小y的纸牌

https://ac.nowcoder.com/acm/problem/22616

原题解链接:https://ac.nowcoder.com/discuss/181037

题目大意:

给出一个序列,分成两个子序列(长度可以不相同,位置可以不连续),使得两个序列各个位置的前缀最大值的和最小。

一道非常巧妙的dp + 非常套路的分块题

首先考虑O(n2)O(n^2)dpdp怎么做

可以发现两个性质: 设原数组为SS 性质11:假设分成的两个序列为a,ba,b。若其中某个位置时,aa中的最大值比bb小,那么以后肯定一直都是aa中的最大值比bb小。因为要使aa中的最大值比bb大,那么只能是有一个s>mxbs > mx_b ​ 且ss被分到了aa数组中。显然,ss分到bb数组中会更优秀

性质22:假设aa数组中的最大值一直比bb小,那么所有分到bb数组中的数字的贡献是原序列中该数字前面所有数字的最大值。因为bb中的最大值一直比aa大,所以这个很显然。

根据上面的两个性质,我们就可以设出状态。设fif_i ​ 表示第ii个数字分到了aa数组且作为aa数组中的最大值最终的贡献。

那么对于原序列SS中第ii个元素我们可以分成以下两种情况,

1.将该元素放到bb数组中。

显然,只有当SiS_i ​ 比aa数组中的最大值大时,我们才可能会选择这种情况。所以我们对于每个Si>SjS_i>S_j ​ 将fj+=max{S1,S2,...Si}f_j+=max\{S_1,S_2,...S_i\}

2.将SiS_i ​ 放到aa数组中。

如果SiS_i ​ 放到aa数组中且a数组中的最大值不变,即Si<=SjS_i<=S_j ​ ,那么将fj+=S[j]f_j+=S[j]即可

如果SiS_i ​ 放到aa数组中aa数组中的最大值变为SiS_i ​ ,这时fi=fj+S[i]f_i=f_j+S[i],只要找到前面最小的一个fjf_j ​ 来更新fif_i ​ 即可

考虑得到了这个O(n2)O(n^2)dpdp之后怎么优化,可以直接对着代码考虑

现在相当于一道数据结构题,需要支持:

查询满足ajaia_j \leqslant a_ifjf_j的最小值

ai>aja_i > a_jfj+=maxi=1iaif_j += max_{i=1}^{i} a_i

aiaja_i \leqslant a_jfj+=ajf_j += a_j

这是一个经典的分块维护凸包的模型,对于每个转移点ii,我们可以将其看做y=aix+fi+by = a_i * x + f_i + b 的形式,fi+bf_i + b是常量,xx是被执行过操作33的次数

接下来可以将每个按照aia_i ​ 分块,因为aiNa_i \leqslant N,所以只要分为N\sqrt{N} ​ 块即可

对于每一块,可以通过打标记来维护操作22(相同块内加的值是相同的)。同时我们可以将块内的每个转移点看做一个函数(也就是y=aix+fi+by = a_i * x + f_i + b),其中自变量xx每次只会+1+1,现在我们只需要知道对于每个xx,这些函数对应的最小值。

也就是说我们只需要维护出红色的线段

alt

这又是个经典模型,,而且考虑到该题比较好的性质,对于每一块只需要从前往后扫一遍,判断一下相邻点的位置关系即可。

查询的时候由于xx单增,只需要判断第一个元素和第二个元素的关系,如果不满足弹出队首即可

复杂度O(NN)O(N \sqrt{N})


#pragma GCC optimize(2)
#include<bits/stdc++.h> 
#define Pair pair<int, LL>
#define MP(x, y) make_pair(x, y)
#define fi first
#define se second
#define int long long
#define pb push_back 
#define LL long long 
#define ull unsigned long long 
#define Fin(x) {freopen(#x".in","r",stdin);}
#define Fout(x) {freopen(#x".out","w",stdout);}
using namespace std;
const int MAXN = 1e6 + 10, mod = 1e9 + 7;
LL INF = 1e18;
const double eps = 1e-9;
template <typename A, typename B> inline bool chmin(A &a, B b){if(a > b) {a = b; return 1;} return 0;}
template <typename A, typename B> inline bool chmax(A &a, B b){if(a < b) {a = b; return 1;} return 0;}
template <typename A, typename B> inline LL add(A x, B y) {if(x + y < 0) return x + y + mod; return x + y >= mod ? x + y - mod : x + y;}
template <typename A, typename B> inline void add2(A &x, B y) {if(x + y < 0) x = x + y + mod; else x = (x + y >= mod ? x + y - mod : x + y);}
template <typename A, typename B> inline LL mul(A x, B y) {return 1ll * x * y % mod;}
template <typename A, typename B> inline void mul2(A &x, B y) {x = (1ll * x * y % mod + mod) % mod;}
template <typename A> inline void debug(A a){cout << a << '\n';}
template <typename A> inline LL sqr(A x){return 1ll * x * x;}
template <typename A, typename B> inline LL fp(A a, B p, int md = mod) {int b = 1;while(p) {if(p & 1) b = mul(b, a);a = mul(a, a); p >>= 1;}return b;}
template <typename A> A inv(A x) {return fp(x, mod - 2);}
inline int read() {
    char c = getchar(); int x = 0, f = 1;
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
int N;
LL a[MAXN];
int block, Mx, ll[MAXN], rr[MAXN], belong[MAXN]; 
LL tag[MAXN], val[MAXN], num[MAXN];//mxa: 块中最大的a tag:每个块额外加的元素 num:每个块的系数 //queue<Pair> que[MAXN];//fi: x  se: b
struct Queue {
	int l, r;
	Pair q[401];
	Queue() {
		l = 1, r = 0;
		memset(q, 0, sizeof(q));
	}
	Pair front() {
		if(l > r) {puts("front is empty");exit(0);}
		return q[l];
	}
	void pop() {
		if(l > r) {puts("front is empty");exit(0);}
		l++;
	}
	bool empty() {
		return l > r;
	}
}que[401];
LL GetV(Pair x, int k) {
	return 1ll * x.fi * k + x.se;
}
LL QueryMin(int id) {//第id个块中的f最小值 
	int &l = que[id].l, &r = que[id].r;
	if(l > r) return INF;
	while(l < r && GetV(que[id].q[l], num[id]) > GetV(que[id].q[l + 1], num[id])) l++;
	LL ans = 1ll * que[id].front().fi * num[id] + tag[id] + que[id].front().se;
	return ans;
}
double GetP(Pair a, Pair b) {
	return (double)(a.se - b.se) / (b.fi - a.fi);
}
Queue Build(int ql, int qr) {
	Queue tq; int &l = tq.l, &r = tq.r; 
	for(int i = qr; i >= ql; i--) {
		while(l <= r && val[i] <= tq.q[r].se) r--;
		while(l < r && GetP({i, val[i]}, tq.q[r - 1]) < GetP(tq.q[r], tq.q[r - 1])) r--;
		tq.q[++r] = {i, val[i]};
	}
	return tq;
}
LL QueryMinF(int x) {//<=x中的f[j]的最小值 
	LL ans = INF;
	for(int i = 1; i <= Mx; i++) {
		if(rr[i] <= x) chmin(ans, QueryMin(i));
		else {
			int id = i;
			for(int j = ll[i]; j <= rr[i]; j++) {
				val[j] += 1ll * j * num[id] + tag[id];
				if(j <= x) chmin(ans, val[j]); 
			}
			num[id] = 0; tag[id] = 0;
			que[id] = Build(ll[id], rr[id]);		
			break;
		}
	}
	return ans;
}
void AddMx(int x, int v) {//a[i] <= x的位置的f全都加上v 
	for(int i = 1; i <= Mx; i++) {
		if(rr[i] <= x) tag[i] += v;
		else {
			int id = i;
			for(int j = ll[i]; j <= rr[i]; j++) {
				val[j] += 1ll * j * num[id] + tag[id];
				if(j <= x) val[j] += v;
			}
			num[id] = 0; tag[id] = 0;
			que[id] = Build(ll[id], rr[id]);	
			break;
		}
	}
}
void AddF(int x) {//a[i] >= x的位置的f全都加上a[i] 
	for(int i = Mx; i >= 1; i--) {
	int id = i;
		if(ll[i] >= x) {
			num[i]++;
		} else {
			for(int j = ll[i]; j <= rr[i]; j++) {
				val[j] += 1ll * j * num[id] + tag[id];
				if(j >= x) val[j] += j;
			}
			num[id] = 0; tag[id] = 0;
			que[id] = Build(ll[id], rr[id]);	
			break;
		}
	}
}
void AddPoint(int x, LL v) {//第x个位置新加入一个值为v的数 
	int id = belong[x];
	for(int j = ll[id]; j <= rr[id]; j++) {
		val[j] += 1ll * j * num[id] + tag[id];
	}
	chmin(val[x], v);
	num[id] = 0; tag[id] = 0;
	que[id] = Build(ll[id], rr[id]);
}
signed main() {
//	freopen("a1.in", "r", stdin);

	N = read(); block = sqrt(N);
	assert(N >= 1 && N <= 70000);
	memset(ll, 0x3f, sizeof(ll));
	memset(val, 0x7f, sizeof(val));
	belong[0] = 1; ll[1] = 0;
	for(int i = 1; i <= N; i++) {
		a[i] = read();
		assert(a[i] >= 1 && a[i] <= N);
		belong[i] = (i - 1) / block + 1;
		chmin(ll[belong[i]], i);
		chmax(rr[belong[i]], i);
		chmax(Mx, belong[i]);
	}
	AddPoint(a[0], 0);
	LL mx = 0;
	for(int i = 1; i <= N; i++) {
		LL mn = QueryMinF(a[i]);; chmax(mx, a[i]);
		AddMx(a[i] - 1, mx);
		AddF(a[i]);
		AddPoint(a[i], mn + a[i]);
	}
	for(int i = 0; i <= N; i++) {
		int id = belong[i];
		val[i] += 1ll * i * num[id] + tag[id];
	}
	LL ans = 1e18;
	for(int i = 0; i <= N; i++) 
		chmin(ans, val[i]);
	cout << ans << '\n';
    return 0;
}
/*

*/


全部评论

相关推荐

诨号无敌鸭:恭喜佬,但是有一个小问题:谁问你了?我的意思是,谁在意?我告诉你,根本没人问你,在我们之中0人问了你,我把所有问你的人都请来 party 了,到场人数是0个人,誰问你了?WHO ASKED?谁问汝矣?誰があなたに聞きましたか?누가 물어봤어?我爬上了珠穆朗玛峰也没找到谁问你了,我刚刚潜入了世界上最大的射电望远镜也没开到那个问你的人的盒,在找到谁问你之前我连癌症的解药都发明了出来,我开了最大距离渲染也没找到谁问你了我活在这个被辐射蹂躏了多年的破碎世界的坟墓里目睹全球核战争把人类文明毁灭也没见到谁问你了
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务