洛谷 P2501 [HAOI2006]数字序列 解题报告
P2501 [HAOI2006]数字序列
题目描述
现在我们有一个长度为n的整数序列A。但是它太不好看了,于是我们希望把它变成一个单调严格上升的序列。但是不希望改变过多的数,也不希望改变的幅度太大。
输入输出格式
输入格式:
第一行包含一个数n,接下来n个整数按顺序描述每一项的键值。
输出格式:
第一行一个整数表示最少需要改变多少个数。
第二行一个整数,表示在改变的数最少的情况下,每个数改变的绝对值之和的最小值。
说明
90%的数据n<=6000。
100%的数据n<=35000。
保证所有数列是随机的。
“数据随机”==乱搞 啊哈
陷入了笛卡尔树的坑里
看了题解,大家一致认为第一问灰常简单,第二问灰常毒瘤
我:
好吧,第一问其实有思想的,发现直接求要改变的死活不好弄,不妨使用补集转换的思想,求最多不改变的数字
设\(dp_i\)代表以\(i\)为末尾的数字不改变时的最大不改变数字
转移有:
\(dp_i=max_{a_i-a_j \ge i-j} dp_j +1\)
复杂度是\(O(N^2)\)的
我们发现,其实我们是在最大化转移次数
如果把转移条件移项\(a_i-i \ge a_j-j\)
设\(b_i=a_i-i\),问题就转换成了求\(LIS\),可以\(O(nlogn)\)求解
第二问 有点微妙 实质上是一个跑不满的\(O(N^3)\)做法,上界极其宽松(当然要写的好才行)
把\(a\)变得单调上升,等价与把\(b\)变的单调不降,花费是等价的
设\(f_i\)为把前\(i\)项合法的最小花费
转移有:
\(f_i=min_{dp_i==dp_j+1} f_j+cost_{i,j}\)
先不考虑如何计算花费,考虑卡枚举前一维的常数
很显然前\(i\)项是要跑满的,从哪里转移我们建一个链表就表示转移集合
考虑如何计算费用
发现如果可以转移,那所有的在\(b_i\)和\(b_i\)之间的\(b\)没有值是夹在它们中间的。
它们一定会往两端进行靠拢,可以证明(没看懂原证明),存在一个\(k\),使\(b_i\)$b_k$都为$b_i$,使$b_k+1$\(b_j\)都为\(b_j\)
所有我们只需要枚举中间的这个\(k\)就行啦
代码细节还是很多的,没给值域还是很坑的
Code:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#define ll long long
ll min(ll x,ll y){return x<y?x:y;}
ll abs(ll x){return x>0?x:-x;}
const ll N=35002;
ll n,a[N],b[N],g[N],k;
std::vector <ll > dx[N];ll dp[N],s1[N],s2[N];
int main()
{
scanf("%lld",&n);dp[0]=-0x3f3f3f3f,b[n+1]=-dp[0],b[0]=dp[0];
for(ll i=1;i<=n;i++)
scanf("%lld",a+i),b[i]=a[i]-i;
for(ll i=1;i<=n;i++)
{
if(b[i]>=dp[k]) dp[++k]=b[i],g[i]=k;
else
{
g[i]=std::upper_bound(dp+1,dp+1+k,b[i])-dp;
dp[g[i]]=b[i];
}
}
printf("%lld\n",n-k);g[++n]=k+1;
for(ll i=0;i<=n;i++) dx[g[i]].push_back(i);
memset(dp,0x3f,sizeof(dp));
dp[0]=0;
for(ll i=1;i<=n;i++)
{
for(ll j=0;dx[g[i]-1][j]<i&&j<dx[g[i]-1].size();j++)
{
ll to=dx[g[i]-1][j];
if(b[i]<b[to]) continue;
for(ll l=to;l<=i;l++) s1[l]=abs((ll)(b[l]-b[to])),s2[l]=abs((ll)(b[l]-b[i]));
for(ll l=to+1;l<=i;l++) s1[l]+=s1[l-1],s2[l]+=s2[l-1];
for(ll l=to;l<=i;l++)
dp[i]=min(s1[l]-s1[to]+s2[i]-s2[l]+dp[to],dp[i]);
}
}
printf("%lld\n",dp[n]);
return 0;
}