题解:AT_ttpc2015_o 数列色ぬり

前言

题目传送门记录

参考资料:讲解

约定:LIS 为最长上升子序列,LDS 为最长下降子序列,

全局的 LIS 的大小记为 \(|LIS|\),LDS 大小记为 \(|LDS|\)

分析

问题简化

此题即求 LIS 与 LDS 的的最大大小。

首先有一个结论:答案只能是 \(|LIS|+|LDS|\)\(|LIS|+|LDS|-1\)

也即是说,二者的交(下文的特指 LIS 和 LDS 的交)的大小最大为 \(1\),理由是二者一个单增,一个单减。

求解 \(|LIS|\)\(|LDS|\) 是容易的,于是,问题就转化为了判定是否存在一种方案,使交为空。

解决方法

考虑在什么情况下交一定非空。

设包含第 \(i\) 个数的 LIS 的长度为 \(LIS_i\),个数为 \(cntLIS_i\),对应地,定义 \(LDS_i\)\(cntLDS_i\)

设全局不同的 LIS 个数为 \(I\),LDS 个数为 \(D\)

那么,二者的交一定非空的充要条件为:

\[\sum_{LIS_i=|LIS|\land LDS_i=|LDS|}cntLIS_i\times cntLDS_i=I\times D \]

$\red{\text{证明}}$

从意义出发,\(I\times D\) 表示全局的 LIS 和 LDS 二元组的组数,

左式中的每一项表示交为第 \(i\) 个数时,LIS 和 LDS 二元组的组数。

由于交的大小不超过 \(1\),所以左式计数不重。


求解 \(LIS_i\) 等可以用前后合并的方法,需要使用树状数组实现快速转移。

考虑到 \(cntLIS_i,cntLDS_i\) 的值可能非常大(比如对于一个第 \(i\) 项为 \(((i-1)\oplus{1})+1\) 的序列,可以达到 \(2^{\frac{n}{2}}\)),

我们需要哈希的方式去存储(手写高精还是不太明智),同时为了防止被卡,可以采用多模哈希。

时间复杂度 \(O(n\log{n})\),空间复杂度 \(O(n)\)

代码

$\red{\text{code}}$
#include<bits/stdc++.h>
using namespace std;
#define N 100005

const int mod[2]={998244353,1000000007};
int A[N],n;

struct res{
	int maxn,cnt[2];
	inline void init(){
		maxn=-N,cnt[0]=0,cnt[1]=0;
	}
	inline res operator +(int a){
		maxn+=a;
		return *this;
	}
	inline res operator *=(res &a){
		maxn+=a.maxn-1;//这里有减一! 
		cnt[0]=1ll*cnt[0]*a.cnt[0]%mod[0];
		cnt[1]=1ll*cnt[1]*a.cnt[1]%mod[1];
		return *this;
	}
	inline res operator +=(res &a){
		cnt[0]+=a.cnt[0],(cnt[0]>=mod[0])&&(cnt[0]-=mod[0]);//与短路取模 
		cnt[1]+=a.cnt[1],(cnt[1]>=mod[1])&&(cnt[1]-=mod[1]);
		return *this;
	}
	inline bool operator ==(res &a){ 
		if(cnt[0]!=a.cnt[0]) return 0;
		return cnt[1]==a.cnt[1];
	}
}dp[2][2][N],LIS,LDS,f;
//第一维 从前向后/从后向前 第二维 LIS/LDS 

inline int read(){
	int a=0;char ch=getchar();
	while(!isdigit(ch)) ch=getchar();
	while(isdigit(ch)) a=10*a+ch-'0',ch=getchar();
	return a;
}

inline void cmax(res &a,res b){
	if(a.maxn<b.maxn) a=b;
	else if(a.maxn==b.maxn){ 
		a.cnt[0]+=b.cnt[0],(a.cnt[0]>=mod[0])&&(a.cnt[0]-=mod[0]);
		a.cnt[1]+=b.cnt[1],(a.cnt[1]>=mod[1])&&(a.cnt[1]-=mod[1]);
	}
}

class BIT{
	private:
	res tr[N],ret;
	inline int lbt(int x){return x&(-x);} 
	public:
	inline void add(int p,res d){
		for(;p<=n+1;p+=lbt(p)) cmax(tr[p],d);
	}
	inline res query(int p){
		for(ret.init();p>=1;p-=lbt(p)) cmax(ret,tr[p]);
		return ret;
	}
	void init(){
		for(int i=1;i<=n+1;i++) tr[i].init();
		add(1,res{0,{1,1}});//初始状态 
	}
}B[2];//1->LDS,0->LIS

void solve(){
	//初始化 
	n=read(),LIS.init(),LDS.init(),f={0,{0,0}};	
	for(int i=1;i<=n;i++) A[i]=read();
	for(int j=0;j<=1;j++){
		for(int k=0;k<=1;k++){
			for(int i=1;i<=n;i++) dp[j][k][i].init();
		}	
	}
	//从前向后计算 
	B[0].init(),B[1].init();
	for(int i=1;i<=n;i++){
		//查询、插入的下标偏移一位,树状数组中存了初始状态 
		cmax(dp[0][0][i],B[0].query(A[i]+1)+1);
		cmax(dp[0][1][i],B[1].query(n-A[i]+2)+1);
		B[0].add(A[i]+1,dp[0][0][i]);
		B[1].add(n-A[i]+2,dp[0][1][i]);
		//顺带统计 
		cmax(LIS,dp[0][0][i]);
		cmax(LDS,dp[0][1][i]);
	}
	//从后向前计算 
	B[0].init(),B[1].init();
	for(int i=n;i>=1;i--){//注意调换下标 
		cmax(dp[1][0][i],B[0].query(n-A[i]+2)+1);
		cmax(dp[1][1][i],B[1].query(A[i]+1)+1);
		B[0].add(n-A[i]+2,dp[1][0][i]);
		B[1].add(A[i]+1,dp[1][1][i]);
	}
	//合并 
	for(int i=1;i<=n;i++){
		dp[0][0][i]*=dp[1][0][i];
		dp[0][1][i]*=dp[1][1][i];
		//判断是否可能同时出现在 LIS 和 LDS 中 
		if(dp[0][0][i].maxn!=LIS.maxn) continue;
		if(dp[0][1][i].maxn!=LDS.maxn) continue;
		dp[0][0][i]*=dp[0][1][i],f+=dp[0][0][i];
	}
	LIS*=LDS;
	if(!(f==LIS)) LIS.maxn++;//由于加的时候减一,此时应是加回 
	printf("%d\n",LIS.maxn);
}

int main(){
 	solve();
	return 0;
} 

posted @ 2026-06-05 10:05  Wxb2010  阅读(6)  评论(0)    收藏  举报