[HAOI2006]数字序列

Description:

给定一个整数序列,每次可以把一个数加大或减小,求:
1.最少操作几个数是原序列严格单增
2.在第一问的前提下,增大减小的总绝对值的最小值是多少

Hint:

\(n \le 10000\)

Solution:

这题是真的难,,,,,,

首先第一问应该都想得到,转化求保留最多数,就有:

\[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;
}

posted @ 2019-03-29 22:15  cloud_9  阅读(161)  评论(0编辑  收藏  举报