最长递增序列 bzoj 1049

首先送几组数据

13
1 1000 2 1001 3 1002 4 1005 5 4 3 2 1
10
8022

13
1 10 2 11 3 12 4 13 5 4 3 2 1
11
92

10
1 2 1 2 1 2 1 2 1 2
8
24

15
1 1000 2000 2 1001 2001 3 1002 2002 2 999 1999 1 998 1998
12
8020

15
1 99987 299986 -4 -3 99989 2 7 6 5 99999 4 4 8 8
10
899966

15
66687 12345 15621 654 21654 6216 4651 6513 218 86 21 1 26 54821321 516
11
54962736

15
-1511 -56262 1546132 561 3215 632 15651 325 16532 51651 3251 6512 151654 8321 9999
8
1823253

15
99115 -695 -65 -6  -5  -2 -5 1151 21 59 555555555 565121 5555555 5555 6666
6
561760518
过了以上数据你就能过了吧我估计至少概率很大了
http://www.bubuko.com/infodetail-1406836.html
上面的题解阔以和我的互补我只说他没说清楚的地方。。。。
还有他比我写的好。。。。
此题关键点:
0.第一问我们就是要转化一下让所有输入的数值都减小他的位置值么因为a[i]-a[j]>=i-j只有这样才有递增的吧那么a[i]-i>=a[j]-j;让b[i]=a[i]-i;(虽然很简单但是很重要)
求最长不减序列就好了
我们定义一个std 先让他的值为std=最后一个数的直吧
1.我们首先考虑任意一个序列我们要把他变成不递减序列怎么办(当然要花费最小)
我们用调整法考虑下首先让所有数字的值都和std值一样,这样记录花费为f那么如何然f更小呢?(当然要保持不递减的性质哦!)我们调整std的大小
先让变小一点点如果原来(首先,1-n中的数比std值小的数目为a1大于等于std值的数字的数目为a2 如果a1>a2那么在我们减小的过程中f1是减小的 2.如果a1=a2
先开始f是不变的只有当调整到a1<a2时或者a1>a2时f改变)运用这种方法我们阔以逐步调整而求出最值(调整方法为1.改变std的值2.改变std管理的区间范围;
  2.我引入调整法的目的并不是解题,用调整法的太复杂了,但我们在调整的过程中应该发现一个规律吧:即只有当std等于其中某一个数的值得时候才会有最值
    当然如果在std不等于其中某一个数时,但是此时有a1=a2我们也阔以条调整值到某一个数反正调整的过程f值也不变么。。
3.通过以上分析我们再来看此题:
当我们确定了一个最长不递增序列时我们就要调整其它数的值了显然(最长不递增序列连续的两个数b1 b2之间的数要么大于等于b1 要么小于等于b2这是一个很重要的性质虽然很显然。。。由2的结论  再加上我们要的是一个不递增数列所以b1 b2之间的数的值要么为b1要么为b2
现在就阔以设计代码进行dp了。。。。
/*
代码设计思路是首先在前后两端插入一个负无穷大和一个正无穷大这样能(这种思路很重要因为代码设计思路不对将导致代码长且易错。。我就是这样被坑的。。)
然后注意一些细节就好了最好先自己设计代码再来看我的
*/
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
int lis[40000];//lis[]就是记录最长升子序的位置
long long  a[40000], b[40000],f[40000],pre[40000],before[40000],stack[40000],first[40000],nextt[40000];//a 是读入数值 b是a[]-i f是记录以某个位置结尾的最长序列
int tot = 0,totnum=-1;                                                                                    //before是此题关键记录比std小和比std大的差值(我们一般让std最先为最后一个数
                                                                                                            //结论3 std只能为最后一个数或者首位
int  pos = 1;                                                                                             //pre是记录花费的 stack什么的就是栈不说了。。。
long long cutmax, sumall = 0;
int totstack = 0;//从0开始就阔以避免初始化first
void insert(int head, int num)
{
	stack[++totstack] = num;
	nextt[totstack] = first[head];
	first[head] = totstack;
}
int erfen(int k)//我感觉我二分写的不错哦。。。
{
	int l = 0;
	int r = tot;
	while (l < r)
	{
		int mid = (l + r) >> 1;
		if (lis[mid] >k)
		{
			r = mid - 1;
		}
		else
		{
			l = mid + 1;
		}
	}
	if (lis[l]<= k&&lis[l]!=-99999999)
		return l+1;
	else
		return l;
}

int main()
{
	int n;
	scanf("%d", &n);
	n++;
	b[0] = -1000000000;
	b[n] = 1000000000;
	for (int i = 1; i < n; i++)
	{
		scanf("%lld", &a[i]);
		b[i] = a[i] - i;
	}
	for (int i = 0; i <= n; i++)
	{
		pre[i] = 1000000000;
		lis[i] = -99999999;
	}
	for (int i = 0; i <= n; i++)
	{
		int l = erfen(b[i]);
		f[i] = l;
		lis[l] = b[i];//传统的lis dp 不懂看挑战程序设计竞赛
		if (l > tot)
		{
			tot++;
		}
		insert(f[i], i);//这方便后面找temppos
	}
	pre[1] = 0;
	pre[0] = 0;
	for (int i = 2; i <=n; i++)//开始dp花费。。。
	{
		int temppos=i;
		for (int j = first[f[i] - 1]; j; j = nextt[j])
		{
			int num = stack[j];
			if (num < i&&b[num] <= b[i])
			{
				temppos = num;
			}
		}//首先找出f[j]=f[i]-1的距离i最远的j
		before[temppos] = 0;
		for (int j = temppos + 1; j < i; j++)
		{
			before[j] = before[j - 1] + ((b[j] < b[i] )? 1 : -1);
		}
		cutmax = -1000000000;
		before[0] = cutmax;
		sumall = 0;
		for (int j = i - 1; j>=temppos; j--)
		{
			if (before[j]>cutmax)
			{
				cutmax = before[j];
				before[0] = cutmax;//预防0点不加这句就呵呵了。。。
			}
			if (f[j] == f[i] - 1 && b[j] <= b[i])
				pre[i] = min(pre[i], pre[j] + sumall - (cutmax - before[j])*(b[i] - b[j]));//这是一个关键sumall是根据最先的std算出的花费(cutmax-before[j])算出阔以减小的最多的值
			sumall += abs(b[i] - b[j]);                                           //自己想先把。。。。我本来想画图的但是。。。。。。
		}
	}
	long long  ans = pre[n];
	long long k = (long long)(n - tot);
	if (n == 0)
		printf("0\n0\n");
	else
	printf("%lld\n%lld\n", k, ans);
	return 0;
 }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值