给定一个整数序列,每次可以把一个数加大或减小,求:
1.最少操作几个数是原序列严格单增
2.在第一问的前提下,增大减小的总绝对值的最小值是多少
\(n \le 10000\)
这题是真的难,,,,,,
首先第一问应该都想得到,转化求保留最多数,就有:
\[f[i]=f[j]+1(a[i]-a[j]>=i-j)\]
把转移条件移项得:
\[a[i]-i>=a[j]-j\]
令\(b[i]=a[i]-i\) ,就是求b的LIS
第二问
我们现在考虑对于两个相邻的\(b[i]\)
他们中间对应的原\(a[i]\)都是不合法的
所以需要调整
首先你要\(yy\)出一个十分不显然的结论:
对于中间一端的\(a[i]\)一定会变成一个分别沿着\(a[l]\)和\(a[r]\)连续\(+1\)的阶梯状
且中间有一个分界点,换句话说,就是他们的\(b[i]\)不是变成b[l]就是\(b[r]\)
故我们可以枚举这个分界点\(k\)来\(dp\),复杂度\(O(n^2)\)可以卡过
#include <map>
#include <set>
#include <stack>
#include <cmath>
#include <queue>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#define ls p<<1
#define rs p<<1|1
using namespace std;
typedef long long ll;
const int mxn=1e5+5,inf=1e9;
int n,m,bd,cnt,ans,hd[mxn];
int a[mxn],b[mxn],mi[mxn],longlen[mxn];
ll f[mxn],suml[mxn],sumr[mxn];
inline int read() {
char c=getchar(); int x=0,f=1;
while(c>'9'||c<'0') {if(c=='-') f=-1;c=getchar();}
while(c<='9'&&c>='0') {x=(x<<3)+(x<<1)+(c&15);c=getchar();}
return x*f;
}
inline void chkmax(int &x,int y) {if(x<y) x=y;}
inline void chkmin(int &x,int y) {if(x>y) x=y;}
struct ed {
int to,nxt;
}t[mxn<<1];
inline void add(int u,int v) {
t[++cnt]=(ed) {v,hd[u]}; hd[u]=cnt;
}
int main()
{
n=read();
for(int i=1;i<=n;++i) a[i]=read(),b[i]=a[i]-i;
b[n+1]=inf;
for(int i=1;i<=n+1;++i) {
int l=0,r=bd;
while(l<r) {
int mid=(l+r+1)>>1;
if(mi[mid]<=b[i]) l=mid;
else r=mid-1;
}
if(l==bd) ++bd; //普通LIS
longlen[i]=l+1; add(longlen[i],i);/*为第二问预处理*/ mi[l+1]=b[i];
}
add(0,0); printf("%d\n",n-(bd-1));
memset(f,0x3f,sizeof(f));
b[0]=-inf; f[0]=0;
for(int i=1;i<=n+1;++i) {
for(int j=hd[longlen[i]-1];j;j=t[j].nxt) {
int v=t[j].to;
if(v>i||b[v]>b[i]) continue ; //转移条件
suml[v]=0;
for(int k=v+1;k<=i-1;++k)
suml[k]=suml[k-1]+abs(b[k]-b[v]); //下阶梯
sumr[i-1]=0;
for(int k=i-2;k>=v;--k)
sumr[k]=sumr[k+1]+abs(b[k+1]-b[i]);//下阶梯
for(int k=v;k<=i-1;++k)
f[i]=min(f[i],f[v]+suml[k]+sumr[k]);
}
}
printf("%lld",f[n+1]);
return 0;
}
原文:https://www.cnblogs.com/list1/p/10624560.html