CF 1437 E. Make It Increasing 改动nlogn LIS + 序列性质分析

探讨如何通过求解最长递增子序列(LIS)来解决序列非严格递增改动问题。文章首先介绍了一个基本问题,即求最少改变次数使序列非严格递增,然后分析在严格递增情况下如何调整策略。接着,文章提出将序列分段,并针对每段求解LIS,最终累加结果。在nlogn的时间复杂度求LIS过程中,详细阐述了动态规划的优化方法,确保子序列满足特定条件。
摘要由CSDN通过智能技术生成

在做这一题之前先看一道题:

给的一个长度为n的序列a,求最少需要改变a的数量,使得a序列非严格递增。

显然结果为:n-LIS,(因为最后一定有些数是不变的,我们让不变的数尽量长,则必须让不变的数满足最终序列的性质即:非递减)

如果把非严格改成严格递增呢?

我们依然用上面的思路:让不变的序列尽量长,即满足最终序列,最终序列是严格递增序列,由于是整数,则对于:j>i,必须满足a[j]>a[i].且a[j]-a[i]>=j-i. 这样才能保证中间变化的数有解。

为了方便我们对上面的式子变化一下:a[j]-j>=a[i]-i. 为了方便: 我们刚开始让a[i]-=i,最终得到的序列就只需要满足j>i,a[j]>a[i],即可,即:满足a序列非严格递增即可。这就回到了第一个问题,直接求即可。

知道上面的经典例题后,我们来看这道题就非常简单了:

首先比较显然的思路:

对于每段b[i-1],b[i],我们把它看成一个区间,求一次结果。最后累加即可。若有一个区间a[R]-a[L]>R-L,则说明无法构造成功,输出-1即可。为了方便可在首尾加上两个哨兵节点。

另外:对于每段区间L,R。我们求得是R-L-1-LIS。

其中LIS是必须要包括a[L],和a[R]的。所以在nlogn求LIS的过程中我们需要改动一下:

首先我们要对nlogn求LIS的过程比较熟练:

dp[i]表示长度为i的非严格递增子序列结尾最小值为多少。

每次从前往后遍历,若二分到最后一个一个大于dp的位置tp,然后插到其后面,改变的dp[tp+1]的值,表示当前数接在长度为tp结尾为dp[tp]的LIS后面可以更新长度为tp+1的LIS的结尾值。稍微分析下可知,只需要改变这一个位置。其他的长度,要么无法改变,要么改变了不是最优解。

那么这题要怎么办呢?

我们可以强制让组成LIS的每个元素值必须大于等于a[L],小于等于a[R],这样刚好能保证第一个和最后一个必选。。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define re register
#define ls (o<<1)
#define rs (o<<1|1)
//#define m (l+r)/2
#define pb push_back
typedef pair<int,int> pii;
const double PI= acos(-1.0);
const int M = 5e5+7;
/*
int head[M],cnt=1;
void init(int n){cnt=1;for(int i=0;i<=n;i++)head[i]=0;}
struct EDGE{int to,nxt,w;}ee[M*2];
void add(int x,int y,int w){ee[++cnt].nxt=head[x],ee[cnt].w=w,ee[cnt].to=y,head[x]=cnt;}
*/
int b[M],a[M],dp[M];
int n,k;

int gao(int L,int R){
	int up=1;
	for(int i=0;i<=R-L+1;i++)dp[i]=1e9+7;
	dp[1]=a[L];
	for(int i=L+1;i<=R;i++){
		if(a[i]>a[R])continue;//必须保证最后一个必选 
		int l=1,r=up,tp=0;
		while(l<=r){
			int m=(l+r)/2;
			if(dp[m]<=a[i])l=m+1,tp=m;
			else r=m-1;
		}
		if(tp==0)continue;//保证L不会被更新,即L必选 
		dp[tp+1]=a[i];
		up=max(up,tp+1);
	}
//	cout<<" - "<<L<<" "<<R<<" "<<up<<"  =  "<<R-L+1-up<<endl;
	return R-L+1-up;
}
int main()
{
	ios::sync_with_stdio(false);
  	cin.tie(0);
  	cin>>n>>k;
  	for(int i=1;i<=n;i++){
  		cin>>a[i];
  		a[i]-=i;
	}
/*	for(int i=1;i<=n;i++){
		cout<<a[i]<<" ";
	}
	cout<<endl;*/ 
	for(int i=1;i<=k;i++)cin>>b[i];
	int ans=0;
	b[k+1]=n+1;
	b[0]=0;
	a[n+1]=1e9+1;
	a[0]=-1e9-1;
	for(int i=1;i<=k+1;i++){
		if(a[b[i]]<a[b[i-1]]){
			cout<<-1<<endl;
			return 0;
		}
	//	cout<<i<<" "<<ans<<endl;
		ans+=gao(b[i-1],b[i]);
	}
	cout<<ans<<endl;
	return 0;
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值