P2501 [HAOI2006]数字序列
题意
给定一个\(n\)个数的序列,问最少改变多少个数能让它变成一个单调严格上升的序列,且让每个数改变的绝对值之和最小,输出个数和最小值
\(1\le n\le3.5*10^4,1\le a_i\le 10^5\)
思路
- \(Problem\ 1\)
看懂题目之后,淦,这什么东西啊
我陷入了满足的数应该被改变的思考中......嗯,想不出来
正着想不出来可以反着想,考虑满足什么条件的数应该被保留
如果\(a[i]\)和\(a[j]\)之间的数本身合法或者能被改成合法的那么它们就是应该被保留的
容易得出满足\(a[j]-a[i]\ge j-i\)
即\(a[j]-j\ge a[i]-i\)
显然,可以令\(b[i]=a[i]-i\),然后求\(b\)的最大不下降子序列(因为条件是\(\ge\))的长度\(len\),\(n-len\)即为第一问答案
- \(Problem\ 2\)
我们继续在\(b\)上考虑,对于一对被保留的点\(b[i]\)和\(b[j]\),他们之间的数一定都是不合法
现在的问题就在于如何把这些数都改变才能使改变量最小
一个结论
设分界点为\(k\),则对于左边\(b[l] \to b[i],l\in[i,k]\)对于右边\(b[r]\to b[j],r\in [k+1,j]\)
证明的话就不写了,我也不会
设\(g[i]\)表示\([1,i]\)的在改变次数最小时最小改变量之和
\(g[i]=\min(g[j]+w(j+1,i))\)
根据上面的结论可以通过前缀和和后缀和来优化求\(w(j+1,i)\)的过程
复杂度为\(O(n^2+nlogn)\),期望得分:\(100\)
/*
@ author:pyyyyyy/guhl37
-----思路------
-----debug-------
*/
#include<bits/stdc++.h>
#include<algorithm>
#define int long long
using namespace std;
const int N=35005;
const int M=200005;
struct node
{
int Next,to;
}e[N<<1];
int head[N],cnt;
void add(int u,int v)
{
e[++cnt].Next=head[u];
head[u]=cnt;
e[cnt].to=v;
}
int n,a[N],f[N],g[N],pre[N],suf[N];
int st[N],top;
void solve1()
{
f[0]=0;a[0]=-0x3f3f3f;a[++n]=0x3f3f3f;
f[1]=1;st[1]=a[1];top=1;
for(int i=2;i<=n;++i)
{
if(st[top]<=a[i]) st[++top]=a[i],f[i]=top;
else
{
int tmp=upper_bound(st+1,st+1+top,a[i])-st;
st[tmp]=a[i];f[i]=tmp;
}
}
cout<<n-f[n]<<'\n';
}
void solve2()
{
memset(g,0x3f3f3f,sizeof(g));g[0]=0;
for(int i=0;i<=n;++i) add(f[i],i);//注意这里要从f[0]开始建立映射
for(int i=1;i<=n;++i)
{
for(int j=head[f[i]-1];j;j=e[j].Next)
{
int to=e[j].to;
if(to>i||a[to]>a[i]) continue;
for(int k=to;k<=i;++k) pre[k]=abs(a[k]-a[to]),suf[k]=abs(a[k]-a[i]);
for(int k=to;k<=i;++k) pre[k]+=pre[k-1],suf[k]+=suf[k-1];
for(int k=to;k<i;++k) g[i]=min(g[i],g[to]+pre[k]-pre[to]+suf[i]-suf[k]);
}
}
cout<<g[n]<<'\n';
}
signed main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
cin>>n;
for(int i=1;i<=n;++i) scanf("%lld",&a[i]),a[i]-=i;
solve1();
solve2();
return 0;
}
$$Life \quad is \quad fantastic!$$