题解: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;
}
洛谷账号 https://www.luogu.com.cn/user/1415227

浙公网安备 33010602011771号