nssl1488-上升子序列【贪心,dp】

正题


题目大意

长度为 n n n的序列,分割成两个上升子序列要求长度差最小


解题思路

我们对于 i < j , a i ≥ a j ii<j,aiaj的点之间连边,然后可以对于一个联通块进行二分图染色,我们可以发现,如果我们先固定一个点的颜色,那么这个联通块的二分图染色方案数为1或0。

之后两个联通块互不影响,所以我们可以用 d p dp dp来计算方案。

考虑继续优化,我们可以发现如果 i i i j j j之间有连边,那么对于任意一个 i < k < j ii<k<j i i i k k k j j j必定联通,那么我们可以推得一个联通块必定是一个区间,而一个区间的分界点满足该点前面的所有数都比后面的所有数要小。

因为划分方案数为 2 c n t 2^{cnt} 2cnt(cnt为联通块数量。所以联通块数量不超过 l o g ( 1 e 18 ) log(1e18) log(1e18),可以通过本题


c o d e code code

#include
#include
#include
#include
using namespace std;
const int N=1e5+10,K=300;
int T,n,a[N],maxs[N],mins[N];
int cnt,b[K],e[K],z[K];
bool f[2][N];
stack<int> q1,q2;
int main()
{
	//freopen("sample2.in","r",stdin);
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			maxs[i]=max(maxs[i-1],a[i]);
		}
		mins[n+1]=2147483647/3;cnt=0;
		for(int i=n;i>=1;i--)
			mins[i]=min(mins[i+1],a[i]);
		for(int i=1;i<=n;i++)
			if(maxs[i]<=mins[i+1]||i==n)
				b[++cnt]=e[cnt-1]+1,e[cnt]=i;
		bool flag=0;
		for(int i=1;i<=cnt;i++){
			while(!q1.empty())q1.pop();
			while(!q2.empty())q2.pop(); 
			for(int j=b[i];j<=e[i];j++){
				if(q1.empty()||a[j]>q1.top())q1.push(a[j]);
				else if(q2.empty()||a[j]>q2.top())q2.push(a[j]);
				else{flag=1;break;}
			}
			if(flag)break;
			z[i]=q1.size()-q2.size();
		}
		if(flag){
			printf("-1\n");
			continue;
		}
		memset(f,0,sizeof(f));
		f[0][0]=1;
		for(int i=1;i<=cnt;i++)
			for(int j=0;j<=n;j++)
				f[i&1][j]=f[~i&1][abs(j-z[i])]|f[~i&1][abs(j+z[i])];
		for(int j=0;j<=n;j++)
			if(f[cnt&1][j]){
				printf("%d\n",j);
				break;
			}
	}
}

你可能感兴趣的:(dp,贪心)