[HAOI2006]数字序列
做题时间:2022.7.19
\(【题目描述】\)
有一个长为 \(N(1\leq N\leq 3.5\times 10^4)\) 随机生成的序列 \(a\) ,现将其变成一个最长上升序列,问其最少修改几个数及最小的修改幅度是多少?(修改幅度即所有修改后的数与修改前的值之差的绝对值之和)
\(【输入格式】\)
第一行一个整数 \(N\)
第二行 \(N\) 个整数表示 \(a\)
\(【输出格式】\)
第一行表示第一问答案
第二行表示第二问答案
\(【考点】\)
最长XX子序列、动态规划
\(【做法】\)
第一问
直接DP很不容易,考虑反向思考,求出最多有多少个数不需要修改,设 \(f_{i}\) 表示前 \(i\) 个数,在 \(i\) 强制不修改的情况下最多有多少个数不需要修改,对于一个 \(j\) 而言,要让它是 \(i\) 上一个不修改的数,则必定要满足 \(a_i-a_j\geq i-j\) ,因此有:
考虑优化:
\(a_i-a_j\geq i-j \rightarrow\) \(a_{i}-i\geq a_j-j\)
会发现,如果令 \(b_x=a_x-x\) ,那么转移条件就会变成 \(b_i\geq b_j\) ,这便是最长不下降子序列的转移方程式,因此用 \(O(N\log N)\) 的时间复杂度求出 \(b_i=a_i-i\) 的最长不下降子序列长度 \(l\) ,最终答案便是 \(N-l\)
第二问
考虑延续第一问的思路,记录下每一个点 \(i\) 由哪些点转移过来的(注意可能不止一个),假设是 \(pre\),那么 \([pre+1,i-1]\) 中的每一个数都不在 \([b_{pre},b_i]\) 区间中。
接下来则考虑 \([pre+1,i-1]\) 的代价。
这里有一个结论:必定存在一个 \(k\) 满足 \([pre+1,i-1]\) 中 \(k\) 左边的 \(b\) 全部等于 \(b_{pre}\) , \(k\) 及 \(k\) 右边的 \(b\) 全部等于 \(b_{i}\)
证明:
考虑使用数学归纳法,令命题 \(P(x)\) 表示 \([pre+1,i-1]\) 中有 \(x\) 个数时上述结论是正确的。
首先 \(P(1)\) 肯定成立,假设现在 \(P(n-1)\) 成立,考虑第 \(n\) 个数的改变:
- 若 \(b_{n}>b_{i}\) ,则肯定变成 \(b_i\) 最好,\(P(n)\) 成立
- 若 \(b_{n}<b_{pre}\) ,设 \(b_n\) 变成了 \(b_{n}+k(b_n+k\neq b_i)\) ,那么为了保证序列的单调不降,在 \(n\) 之前的所有改成 \(b_i\) 的数都要改成 \(b_n\) ,假设这样的数有 \(x\) 个,于是代价便是: \(x\cdot (b_n+k-b_{pre})+val_{n-1}\);而将 \(b_n\) 变成 \(b_i\) 的代价则为:\(val_{n-1}+b_i-b_n\) ,手推一下发现前者明显大于等于后者,因此 \(P(n)\) 成立。
知道了这个结论,我们可以定义 \(g_i\) 表示代价,则有:
后面两个求和可以用前缀和搞定。
不过复杂度是 \(O(N^2)\) 的,但是 \(a_i\) 是随机而且 \(N\) 只有四次方级别,因此是可以过的。
另外要注意的是,由于 \(f_i\) 及 \(g_i\) 表示的是在不修改 \(b_i\) 的情况下的答案,因此最后应当推到 \(b_{n+1}=\inf\)
\(【代码】\)
#include<cstdio>
#include<iomanip>
#include<vector>
#include<cstring>
#include<algorithm>
#define INF 0x7f7f7f7f
using namespace std;
typedef long long ll;
const int N=3e4+5e3+50;
int a[N],d[N],b[N],l[N],g[N],n,len;
ll pre[N],suf[N];
vector<int> p[N];
inline int Abs(int x){return x>0?x:(-x);}
inline ll Min(ll a,ll b){return a<b?a:b;}
void Init(int l,int r)
{
pre[l]=0,suf[r-1]=0;
for(int i=l+1;i<=r-1;i++) pre[i]=pre[i-1]+(ll)Abs(b[l]-b[i]);
for(int i=r-1;i>=l;i--) suf[i]=suf[i+1]+(ll)Abs(b[r]-b[i+1]);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
b[i]=a[i]-i;
}
d[len=1]=b[1],l[1]=1,b[n+1]=INF;
p[1].push_back(1);
//p[x].push_back(y)表示长度为x的最长不下降子序列的末尾元素为y
for(int i=2;i<=n+1;i++){
if(b[i]>=d[len]){
d[++len]=b[i];
p[len].push_back(i);
l[i]=len;
}
else{
int pos=upper_bound(d+1,d+1+len,b[i])-d;
d[pos]=b[i];
p[pos].push_back(i);
l[i]=pos;
}
}
printf("%d\n",n-len+1);
memset(g,0x3f3f3f3f,sizeof g);
p[0].push_back(0);
g[0]=0,b[0]=-INF,b[n+1]=INF;
for(int i=1;i<=n+1;i++){
int len=p[l[i]-1].size();
for(int j=0;j<len;j++){
int pe=p[l[i]-1][j];
if(pe>i||b[pe]>b[i]) continue;
Init(pe,i);
for(int k=pe;k<=i-1;k++){//枚举k
g[i]=Min(g[i],g[pe]+pre[k]+suf[k]);
}
}
}
printf("%d\n",g[n+1]);
return 0;
}

浙公网安备 33010602011771号