1 条题解

  • 0
    @ 2025-8-24 23:06:30

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar liangbowen
    不能再摆了,,,

    搬运于2025-08-24 23:06:30,当前版本为作者最后更新于2024-12-25 22:24:38,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    blog。写了好几天,人都要死了。提供一个不同的切入口,方便大家理解这个分段是在干嘛,以及一个更容易的线段树 DS。题解一堆废话,大家看看就行(

    O(N3)O(N^3)

    先把 ai1a_i\ne-1 且无论如何无法成为前缀 max 的位置 ban 掉。

    由于答案只与 premax 的位置有关,于是dpi,jdp_{i,j} 表示确定完 a1,a2,,aia_1,a_2,\cdots,a_i 且 premax 的位置为 jj 的答案。

    考虑转移 dpi,jdp_{i,j}

    • ai=1a_i=-1
      • [1-1] 随便放个以后不用的小的:dpi,jdpi1,jdp_{i,j}\gets dp_{i-1,j}
      • [1-2] 若上一个 premax 比 jj 小,现在必须贪心地放 jj(要求 visj=truevis_j=\textbf{true}):dpi,jmaxw=0j1dpi1,w+cjdp_{i,j}\gets\max\limits_{w=0}^{j-1}dp_{i-1,w}+c_j
    • ai1a_i\ne-1:可以发现,只需要考虑 jaij\ge a_i 的情况。
      • [2-1] 我要我要!这个时候强制 j=aij=a_i:$dp_{i,a[i]}\gets\max\limits_{w=0}^{a[i]-1}dp_{i-1,w}+c_{a[i]}$。
      • [2-2] 不要不要!这需要保证前面的 premax 比 aia_i 大:dpi,jdpi1,j  (ai<jn)dp_{i,j}\gets dp_{i-1,j}\ \ (a_i<j\le n)

    初始化 -\inftydp0,0=0dp_{0,0}=0;第 KK 个答案即为 maxi=0ndpK,i\max\limits_{i=0}^n dp_{K,i}

    const int N = 4e5 + 5;
    int n, a[N], c[N]; bool vis[N]; ll dp[2005][2005];
    int main() {
    	scanf("%d", &n);
    	for (int i = 1; i <= n; i++) scanf("%d", &a[i]), ~a[i] ? (vis[a[i]] = true) : 0;
    	for (int i = 1; i <= n; i++) scanf("%d", &c[i]);
      
    	for (int i = 1, p = 0; i <= n; i++)
    		if (~a[i]) {if (p > a[i]) c[a[i]] = 0;} else {while (vis[++p]);}
    
    	mems(dp, -0x3f), dp[0][0] = 0;
    	for (int i = 1; i <= n; i++) {
    		if (~a[i]) {
    			for (int j = 0; j < a[i]; j++) dp[i][a[i]] = max(dp[i][a[i]], dp[i - 1][j] + c[a[i]]);
    			for (int j = a[i] + 1; j <= n; j++) dp[i][j] = dp[i - 1][j];
    		} else {
    			for (int j = 1; j <= n; j++) {
    				dp[i][j] = dp[i - 1][j];
    				if (!vis[j]) {for (int k = 0; k < j; k++) dp[i][j] = max(dp[i][j], dp[i - 1][k] + c[j]);}
    			}
    		}
    		printf("%lld ", *max_element(dp[i], dp[i] + n + 1));
    	}
    	return 0;
    }
    

    O(N2)O(N^2)

    前缀 max 容易优化至 O(n2)O(n^2)。由于过一会要上 DS,我们先把 code 变好看一点:

    1. 钦定 visj=falsevis_j=\textbf{false}cj=c^{\prime}_j=-\infty 否则 cj=cjc^{\prime}_j=c_j。于是可以删个 [1-2] 的判断条件。

    2. fi,jf_{i,j} 表示确定完 a1,a2,,aia_1,a_2,\cdots,a_i 且 premax 的位置为 j\le j 的答案。即 fi,j=maxw=0jdpi,wf_{i,j}=\max\limits_{w=0}^j dp_{i,w}。注意到可以全程依赖 ff 进行转移!只要在每次结束后进行 [3-1] 前缀 chkmax 操作就行。

    const int N = 4e5 + 5;
    int n, a[N], c[N]; bool vis[N]; ll ccc[N], f[2005][2005];
    int main() {
    	scanf("%d", &n);
    	for (int i = 1; i <= n; i++) scanf("%d", &a[i]), ~a[i] ? (vis[a[i]] = true) : 0;
    	for (int i = 1; i <= n; i++) scanf("%d", &c[i]);
      
    	for (int i = 1, p = 0; i <= n; i++) if (~a[i]) {if (p > a[i]) c[a[i]] = 0;} else {while (vis[++p]);}
    	for (int i = 1; i <= n; i++) ccc[i] = (!vis[i] ? c[i] : -0x3f3f3f3f3f3f3f3f);
    
    	mems(f, -0x3f), mems(f[0], 0);
    	for (int i = 1; i <= n; i++) {
    		if (~a[i]) {
    			f[i][a[i]] = f[i - 1][a[i] - 1] + c[a[i]];
    			for (int j = a[i] + 1; j <= n; j++) f[i][j] = f[i - 1][j];
    		} else {
    			for (int j = 1; j <= n; j++) f[i][j] = max(f[i - 1][j], f[i - 1][j - 1] + ccc[j]);
    		}
    		for (int j = 1; j <= n; j++) f[i][j] = max(f[i][j - 1], f[i][j]); printf("%lld ", f[i][n]);
    	}
    	return 0;
    }
    

    进而把代码改成 1D 形式。可以直接 DS......吗?

    const int N = 4e5 + 5; const ll INF = 0x3f3f3f3f3f3f3f3f;
    int n, a[N], c[N]; bool vis[N]; ll ccc[N], f[N], g[N];
    int main() {
    	scanf("%d", &n);
    	for (int i = 1; i <= n; i++) scanf("%d", &a[i]), ~a[i] ? (vis[a[i]] = true) : 0;
    	for (int i = 1; i <= n; i++) scanf("%d", &c[i]);
    
    	for (int i = 1, p = 0; i <= n; i++) if (~a[i]) {if (p > a[i]) c[a[i]] = 0;} else {while (vis[++p]);}
    	for (int i = 1; i <= n; i++) ccc[i] = (!vis[i] ? c[i] : -INF);
    
    	for (int i = 1; i <= n; i++) {
    		if (~a[i]) {
    			f[a[i]] = f[a[i] - 1] + c[a[i]];
    			for (int j = 0; j < a[i]; j++) f[j] = -INF;
    		} else {
    			for (int j = n; j; j--) f[j] = max(f[j], f[j - 1] + ccc[j]);
    			f[0] = -INF;
    		}
    		for (int j = 1; j <= n; j++) f[j] = max(f[j - 1], f[j]); printf("%lld ", f[n]);
    	}
    	return 0;
    }
    

    Optimize1

    其中一个大问题是 $\forall(j:n\to1)\ f_j=\max(f_j,f_{j-1}+c^{\prime}_j)$ 这一句?这个结构是线段树无法维护的。这个是本题的最大难点。但是打表发现,只要 fj1f_{j-1}\ne-\infty,那么这个 chkmax 一定是后面成功!

    (这是因为当 fj1f_{j-1}\ne-\infty,说明这个值及其后缀的值都是有效的。那么,如果 fj>fj1+cjf_j>f_{j-1}+c^{\prime}_j由于 cc 单调不降,那么我们将 fjf_j 处最大值的贡献更换为任意一个数,必定仍然有 fj>fj1f_j>f_{j-1},这说明 fj1f_{j-1} 可以取更大,矛盾!)

    进一步地,由于 ff 单调不降,所以 fjf_{j}\ne-\infty 的一定是一段后缀。我们可以维护指针 upup 表示这个后缀。

    const int N = 4e5 + 5; const ll INF = 0x3f3f3f3f3f3f3f3f;
    int n, a[N], c[N]; bool vis[N]; ll ccc[N], f[N], g[N];
    int main() {
    	scanf("%d", &n);
    	for (int i = 1; i <= n; i++) scanf("%d", &a[i]), ~a[i] ? (vis[a[i]] = true) : 0;
    	for (int i = 1; i <= n; i++) scanf("%d", &c[i]);
    	
    	for (int i = 1, p = 0; i <= n; i++) if (~a[i]) {if (p > a[i]) c[a[i]] = 0;} else {while (vis[++p]);}
    	for (int i = 1; i <= n; i++) ccc[i] = (!vis[i] ? c[i] : -INF);
    
    	int up = 0;
    	for (int i = 1; i <= n; i++) {
    		if (~a[i]) {
    			f[a[i]] = max(f[a[i]], f[a[i] - 1] + c[a[i]]);
    			for (int j = 0; j < a[i]; j++) f[j] = -INF;
    		} else {
    			for (int j = n; j > up; j--) f[j] = f[j - 1] + ccc[j];
    			f[0] = -INF;
    		}
    		for (int j = 1; j <= n; j++) f[j] = max(f[j - 1], f[j]);
    		for (int j = 1; j <= n; j++) if (f[j] >= 0) {up = j; break;}
    		printf("%lld ", f[n]);
    	}
    	return 0;
    }
    

    Optimize2

    另一个问题是前缀 chkmax。这个反而不困难,我们挖掘代码中的单调性质就能解决。

    先看看 upup。若 ai1a_i\ne-1,那么每次等价于 upmax(up,ai)up\gets\max(up,a_i);否则, ai=1a_i=1 时除了边界情况,upup 都应该不变。这说明 upup 单调不降

    • 对于 ai1a_i\ne-1 的情况,直接写成主动转移的形式($\forall j\in(up,n],f_j\gets\max(f_j,f_{a_i-1}+c_{a_i})$),发现此时前缀 chkmax 操作直接丢掉就行。
    • 对于 ai=1a_i=-1 的情况,发现若 cj=cj\forall c^{\prime}_j=c_j,那么 fj1+cjf_{j-1}+c^{\prime}_j 是单调的(因为 f,cf,c 都单调)!也就是说,所有 cj=c^{\prime}_j=-\infty 的位置的值,都可以变成前面的第一个 cjc^{\prime}_j\ne-\infty 的位置的值。

    自然地,考虑能否将 cj=c^{\prime}_j=-\infty 的位置直接丢掉。

    这显然是可以的!只要将所有 cj=c^{\prime}_j=-\inftyvisj=falsevis_j=\textbf{false} 的位置与其后一个 visj=truevis_j=\textbf{true} 的位置划分成一段,记录 upup 的时候记录两个(一个是真实值,一个是对应到块的值)就行,每个块除了 -\infty 的情况,全部元素都应当是相同的。

    const int N = 4e5 + 5; const ll INF = 0x3f3f3f3f3f3f3f3f;
    int n, m, up, ups, a[N], c[N], bel[N]; bool vis[N]; ll f[N], ccc[N];
    int main() {
    	scanf("%d", &n);
    	for (int i = 1; i <= n; i++) scanf("%d", &a[i]), ~a[i] ? (vis[a[i]] = true) : 0;
    	for (int i = 1; i <= n; i++) scanf("%d", &c[i]);
      
    	for (int i = 1, p = 0; i <= n; i++) if (~a[i]) {if (p > a[i]) c[a[i]] = 0;} else {while (vis[++p]);}
    	for (int i = 1; i <= n; i++) {if (!vis[i]) ccc[++m] = c[i]; bel[i] = m;}
    
    	for (int i = 1; i <= n; i++) {
    		if (~a[i]) {
    			if (ups < a[i]) {
    				int p = bel[a[i]]; ll x = f[p] + c[a[i]];
    				for (int j = 0; j < p; j++) f[j] = -INF;
    				for (int j = p; j <= m; j++) f[j] = max(f[j], x);
    				ups = a[i], up = p;
    			}
    		} else {
    			for (int j = m; j > up; j--) f[j] = f[j - 1] + ccc[j];
    			f[0] = -INF;
    			if (!up) {ups = 1; for (int j = 1; j <= m; j++) if (f[j] >= 0) {up = j; break;}}
    		}
    		printf("%lld ", f[m]);
    	}
    	return 0;
    }
    

    ShiftTag + DS

    终于,启动 DS!最麻烦的一个部分是"区间平移 + 区间加对应位置的数"。前者可以打 Shift Tag,后缀可以维护 fi=vi+liricif_i=v_i+\sum\limits_{l_i}^{r_i} c_i 转化为 rir_i 的区间加。

    有点问题的是区间 chkmax 操作,但由于 ff 有单调性,二分分界点后就能转为区间覆盖。

    const int N = 8e5 + 5, X = 4e5; const ll INF = 0x3f3f3f3f3f3f3f3f;
    int n, m, up, ups, a[N], c[N], bel[N]; bool vis[N]; ll f[N], l[N], r[N], ccc[N], s[N];
    inline ll cal(int x) {return f[x] + s[r[x]] - s[l[x] - 1];}
    int main() {
    	scanf("%d", &n);
    	for (int i = 1; i <= n; i++) scanf("%d", &a[i]), ~a[i] ? (vis[a[i]] = true) : 0;
    	for (int i = 1; i <= n; i++) scanf("%d", &c[i]);
    
    	for (int i = 1, p = 0; i <= n; i++) if (~a[i]) {if (p > a[i]) c[a[i]] = 0;} else {while (vis[++p]);}
    	for (int i = 1; i <= n; i++) {if (!vis[i]) ccc[++m] = c[i]; bel[i] = m;}
    	for (int i = 1; i <= m; i++) s[i] = s[i - 1] + ccc[i];
    
    	int S = 0;
    	for (int i = 0; i <= 4e5; i++) l[i + X - S] = i + 1, r[i + X - S] = i;
    	for (int i = 1; i <= n; i++) {
    		if (~a[i]) {
    			if (ups < a[i]) {
    				int p = bel[a[i]]; ll x = cal(p + X - S) + c[a[i]];
    				for (int j = 0; j < p; j++) f[j + X - S] = -INF;
    				for (int j = p; j <= m; j++) if (x > cal(j + X - S)) f[j + X - S] = x, l[j + X - S] = j + 1, r[j + X - S] = j;
    				ups = a[i], up = p;
    			}
    		} else {
    			f[up - 1 + X - S] = f[up + X - S], l[up - 1 + X - S] = l[up + X - S], r[up - 1 + X - S] = r[up + X - S];
    			for (int j = up; j < m; j++) r[j + X - S]++;
    			S++; f[0 + X - S] = -INF, l[0 + X - S] = 1, r[0 + X - S] = 0; if (!up) up = ups = 1;
    		}
    		printf("%lld ", cal(m + X - S));
    	}
    	return 0;
    }
    

    这样再怎么样你都会做了吧?再维护一个 li=li+i,ri=ri+il^{\prime}_i=l_i+i,r^{\prime}_i=r_i+i 消除 Δ\Delta,然后对 v,l,rv,l^{\prime},r^{\prime} 各维护一棵线段树就行。二分部分在三棵线段树上并行二分就能做到单 log。其余部分只需要区间加、区间覆盖、单点查。

    综上,我们用大常数 O(NlogN)O(N\log N) 通过了此题,被 deque 做法吊打。

    Code

    没啥参考价值的。

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <vector>
    #define mems(x, v) memset(x, v, sizeof x)
    #define mcpy(x, y) memcpy(x, y, sizeof x)
    using namespace std;
    typedef pair <int, int> pii;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef long double wisdom;
    
    const int N = 8e5 + 1005, X = 4e5 + 100; const ll INF = 0x3f3f3f3f3f3f3f3f, NOTVIS = -INF - 114514;
    int n, m, up, ups, a[N], c[N], bel[N]; bool vis[N]; ll ccc[N], s[N];
    
    #define ls pos << 1
    #define rs pos << 1 | 1
    struct SGT {
    	// ll a[N];
    	// void ADD(int l, int r, ll k) {for (int i = l; i <= r; i++) a[i] += k;} void COV(int l, int r, ll k) {for (int i = l; i <= r; i++) a[i] = k;} ll Q(int x) {return a[x];}
    	ll LV[N << 2], add[N << 2], cov[N << 2]; void pushup(int pos) {LV[pos] = LV[ls];} void lcov(int pos, ll k) {cov[pos] = LV[pos] = k, add[pos] = 0;} void ladd(int pos, ll k) {add[pos] += k, LV[pos] += k;}
    	void C(int pos) {if (cov[pos] != NOTVIS) lcov(ls, cov[pos]), lcov(rs, cov[pos]), cov[pos] = NOTVIS;} void A(int pos) {if (add[pos]) ladd(ls, add[pos]), ladd(rs, add[pos]), add[pos] = 0;} void pushdown(int pos) {C(pos), A(pos);}
    	void update(int l, int r, int pos, int L, int R, ll k) {if (L <= l && r <= R) return (l == r ? void() : C(pos)), ladd(pos, k); int mid = (l + r) >> 1; pushdown(pos); if (L <= mid) update(l, mid, ls, L, R, k); if (mid < R) update(mid + 1, r, rs, L, R, k); pushup(pos);}
    	void modify(int l, int r, int pos, int L, int R, ll k) {if (L <= l && r <= R) return lcov(pos, k); int mid = (l + r) >> 1; pushdown(pos); if (L <= mid) modify(l, mid, ls, L, R, k); if (mid < R) modify(mid + 1, r, rs, L, R, k); pushup(pos);}
    	ll query(int l, int r, int pos, int id) {if (l == r) return LV[pos]; int mid = (l + r) >> 1; pushdown(pos); return id <= mid ? query(l, mid, ls, id) : query(mid + 1, r, rs, id);}
    	inline void ADD(int l, int r, ll k) {if (l <= r) update(1, 801000, 1, l, r, k);} inline void COV(int l, int r, ll k) {if (l <= r) modify(1, 801000, 1, l, r, k);} ll Q(int x) {return query(1, 801000, 1, x);}
    	inline void ADD(int x, ll k) {ADD(x, x, k);} inline void COV(int x, ll k) {COV(x, x, k);}
    } F, L, R;
    
    inline ll cal(int x) {return F.Q(x) + s[R.Q(x) + x] - s[L.Q(x) + x - 1];}
    int fnd(int l, int r, int pos, int pl, int pr, ll x) {
    	if (l == r) return l; int mid = (l + r) >> 1; F.pushdown(pos), L.pushdown(pos), R.pushdown(pos);
    	if (mid >= pr) return fnd(l, mid, ls, pl, pr, x); if (pl > mid) return fnd(mid + 1, r, rs, pl, pr, x);
    	return F.LV[rs] + s[R.LV[rs] + (mid + 1)] - s[L.LV[rs] + (mid + 1) - 1] >= x ? fnd(l, mid, ls, pl, pr, x) : fnd(mid + 1, r, rs, pl, pr, x);
    }
    int main() {
    	scanf("%d", &n);
    	for (int i = 1; i <= n; i++) scanf("%d", &a[i]), ~a[i] ? (vis[a[i]] = true) : 0;
    	for (int i = 1; i <= n; i++) scanf("%d", &c[i]);
    
    	for (int i = 1, p = 0; i <= n; i++) if (~a[i]) {if (p > a[i]) c[a[i]] = 0;} else {while (vis[++p]);}
    	for (int i = 1; i <= n; i++) {if (!vis[i]) ccc[++m] = c[i]; bel[i] = m;}
    	for (int i = 1; i <= m; i++) s[i] = s[i - 1] + ccc[i];
    
    	for (int i = 0; i < (N << 2); i++) F.cov[i] = L.cov[i] = R.cov[i] = NOTVIS;
    	int S = 0;
    	for (int i = 0; i <= 4e5 + 100; i++) L.COV(i + X - S, S - X + 1), R.COV(i + X - S, S - X);
    	for (int i = 1; i <= n; i++) {
    		if (~a[i]) {
    			if (ups < a[i]) {
    				int p = bel[a[i]]; ll x = cal(p + X - S) + c[a[i]];
    				//int lo = p + X - S, hi = m + X - S; while (lo < hi) {int md = (lo + hi + 1) >> 1; cal(md) < x ? lo = md : hi = md - 1;}
    				int lo = fnd(1, 801000, 1, p + X - S, m + X - S, x);
    				F.COV(p + X - S, lo, x), L.COV(p + X - S, lo, S - X + 1), R.COV(p + X - S, lo, S - X);
    				ups = a[i], up = p;
    			}
    		} else {
    			F.COV(up - 1 + X - S, F.Q(up + X - S)), L.COV(up - 1 + X - S, L.Q(up + X - S) + 1), R.COV(up - 1 + X - S, R.Q(up + X - S) + 1);
    			R.ADD(up + X - S, m + X - S, 1);
    			S++; F.COV(0 + X - S, -INF), L.COV(0 + X - S, 1 - (0 + X - S)), R.COV(0 + X - S, 0 - (0 + X - S)); if (!up) up = ups = 1;
    		}
    		printf("%lld ", cal(m + X - S));
    	}
    	return 0;
    }
    
    • 1

    信息

    ID
    11013
    时间
    1000ms
    内存
    500MiB
    难度
    7
    标签
    递交数
    0
    已通过
    0
    上传者