*题解:P2501 [HAOI2006] 数字序列

原题链接

解析

第一问

要让改变的数尽可能少就是要让不改变的数尽可能多。考虑指定某些位置上的数不改变,设 \(pos\) 里从小到大排列着不改变的位置,我们发现一种方案不合法当且仅当相邻两个不改变位置上的数的差小于下标差,即 $ a_{{pos_i + 1}}-a_{pos_i} < pos_{i + 1} - pos_i$,这意味着中间的数相较于可取的值太多了,无论怎么改都无法单调严格上升。

所以对于一个合法的方案,必定满足 $ a_{{pos_i + 1}}-a_{pos_i} \ge pos_{i + 1} - pos_i$。

考虑如何求 \(pos\),设 \(f_i\) 表示保证位置 \(i\) 不改变的情况下,前 \(i\) 个位置中不改变位置数量的最大值。

暴力转移:

\[f_i=1+\max_{\substack{j=1\\ a_i - a_j \ge i - j}}^{i - 1} f_j \]

进一步优化需要对式子进行变形,将含 \(i\) 的项放到一边,含 \(j\) 的放到另一边,变为:

\[a_i - i\ge a_j - j \]

到这一步你应该就有思路了,如果还看不出来的话,令 \(b_i=a_i - i\),原来的约束变为 \(b_i \ge b_j\),这其实就是在求最长不下降子序列。

第二问

对于第一问中的一次转移 \(f_i = f_j + 1\),考虑同时转移操作次数,设为 \(g\),则 \(g_i=g_j+\operatorname{cnt}(j,i)\),其中 \(\operatorname{cnt}(j,i)\) 表示将下标在 \([j,i]\) 之间的数组成的序列变得不下降所需的最小步数。

考虑如何求 \(\operatorname{cnt}\)

通过画图观察,我们可以得出对于所有满足 \(i<k<j\)\(k\),有 \(b_k < b_i\)\(b_k>b_j\)。否则 \(b_k\) 也可以加入到不下降子序列当中。

考虑最优解的形态,对于一个极长连续段,如果大于 \(b_j\) 的数的个数比小于 \(b_i\) 的数的个数更多,则令该连续段贴合右侧的连续段必定不劣,新构成的连续段又可以再次进行操作,可以发现,最终形态必定为一个所有数都为 \(b_i\) 的连续段拼上一个所有数都为 \(b_j\) 的连续段。

于是

\[\operatorname{cnt}(i,j)=\min_{i\le k < j} \sum_{t=i}^k\lvert b_i-b_t\rvert + \sum_{t=k + 1}^j\lvert b_j - b_t \rvert \]

对于两个求和,可以分别处理出前半部分的前缀和与后半部分的后缀和,这样在枚举 \(k\) 的时候就可以 \(O(1)\) 得出结果。

第一问时间复杂度为 \(O(n \log n)\),第二问为 \(O(n ^ 3)\),但由于数据随机,所以总时间复杂度为 \(O(能过)\)

代码

方便起见可以在序列开头和末尾分别添加一个负无穷和正无穷的元素。

#include <bits/stdc++.h>
#define ls(p) ((p) << 1)
#define rs(p) (((p) << 1) | 1)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N = 3.5e4 + 5,M = 6e6,mod = 998244353;
int a[N],b[N],mn[N],p[N];
ll g[N],pre[N],suf[N];
vector<int> v[N];//v[i] 表示长度为 i 的不上升子序列中,最优情况下可能的结尾元素下标。
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	memset(g,127,sizeof(g));
	memset(mn,127,sizeof(mn));
	int n;
	cin>>n;
	n++;
	for(int i=2;i<=n;i++){
		cin>>a[i];
		b[i] = a[i] - i;
	}
	a[1] = -2e9;
	b[1] = a[1] - 1;
	n++;
	a[n] = 2e9;
	b[n] = a[n] - n;
	int len = 0;
	for(int i=1;i<=n;i++){
		int pos = upper_bound(mn + 1,mn + len + 1,b[i]) - mn;
		mn[pos] = b[i];
		v[pos].push_back(i);
		p[i] = pos;//i 处元素作为长度为 pos 的不下降子序列的结尾
		if(pos > len) len++;
	}
	for(int i=1;i<=n;i++){
		if(p[i] == 1){
			g[i] = 0;
			continue;
		}
		for(int l=0;l<v[p[i] - 1].size();l++){//枚举可能的合法前驱
			int j = v[p[i] - 1][l];
			if(b[j] > b[i]) continue;//判断 b[i] 能否拼接上 b[j]
			pre[j - 1] = 0;
			for(int t=j;t<=i;t++){
				pre[t] = pre[t - 1] + abs(b[j] - b[t]);
			}
			suf[i + 1] = 0;
			for(int t=i;t>=j;t--){
				suf[t] = suf[t + 1] + abs(b[i] - b[t]);
			}
			for(int k=j;k<i;k++){
				g[i] = min(g[i],g[j] + pre[k] + suf[k + 1]);
			}
		}
	}
	cout<<n - len<<"\n"<<g[n];
	return 0;
}
posted @ 2026-04-10 01:29  yutar  阅读(20)  评论(0)    收藏  举报