#背包#nssl 1488 上升子序列

题目

给一个长度为\(n\)的数组\(a\)。试将其划分为两个严格上升子序列,并使其长度差最小。


分析

\(max([1,i])<min([i+1,n])\)时显然两个区间互不影响,把\(i\)视为分界点
若相邻的两个分界点\(i,j\)所组成的区间\([i\sim j]\)如果合法只有一种划分方法
所以把合法区间贡献扔入背包里搞一搞就可以了


代码

#include <cstdio>
#include <cctype>
#include <bitset> 
#define rr register
using namespace std;
const int N=100011; bitset<N>dp;
int n,mx[N],mn[N],a[N],flag,ans,las;
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
inline void print(int ans){
	if (ans>9) print(ans/10);
	putchar(ans%10+48);
}
inline bool check(int l,int r){
	rr int fi=0,se=0,TOT=0;
	for (rr int i=l;i<=r;++i)
	if (fi<a[i]) fi=a[i],++TOT;
	    else if (se<a[i]) se=a[i];
	        else return 0;
	dp=(dp<<TOT)|(dp<<(r-l+1-TOT));
    return 1;
}
signed main(){
	for (rr int Test=iut();Test;--Test){
		n=iut(),mx[0]=-1,mn[n+1]=0x3f3f3f3f,
		flag=0,dp.reset(),dp[0]=las=1,ans=-1;
		if (n==1) printf("-1\n");
		for (rr int i=1;i<=n;++i) a[i]=iut();
		for (rr int i=1;i<=n;++i) mx[i]=mx[i-1]>a[i]?mx[i-1]:a[i];
		for (rr int i=n;i>=1;--i) mn[i]=mn[i+1]<a[i]?mn[i+1]:a[i];
		for (rr int i=1;i<=n;++i)
		if (mx[i]<mn[i+1]){
			if (!check(las,i)){
				flag=1;
				break;
			}
			las=i+1;
		}
		if (!flag){
			for (rr int i=n/2;~i;--i)
			if (dp[i]){
				ans=n-i*2;
				break;
			}
		}
		printf("%d\n",ans);
	}
	return 0;
} 
posted @ 2020-08-18 14:47  lemondinosaur  阅读(87)  评论(0编辑  收藏  举报