【CCPC2024女生赛】【LIS计数】D. Excellent Splitting

题目

传送门
给你一个排列,你需要把它划分成两个子序列 A、B。记 f ( A ) f(A) f(A) 为 A 的 LIS 长度, g ( B ) g(B) g(B) 是 B 的 LDS 长度。问 m a x ( f ( A ) + g ( B ) ) max(f(A)+g(B)) max(f(A)+g(B))

前置知识 LIS计数

大家都知道,我们可以通过二分, O ( n l o g n ) O(nlogn) O(nlogn) 实现求 L I S LIS LIS 的长度。但是此时要计数怎么办呢?
我们不妨把 d p dp dp 数组扩展成二维。令 d p i [ ] dp_i[] dpi[] 为:长度为 i i i L I S LIS LIS 可能以哪些数结尾。
我们把 d p t = a i dp_t=a_i dpt=ai 改成 d p t . a p p e n d ( a i ) dp_t.append(a_i) dpt.append(ai),也就是把 a i a_i ai 接在后面。
同时,我们需要维护以 a i a_i ai 结尾的 L I S LIS LIS 的方案数,记为 F i F_i Fi
显然,这玩意个转移是:
F i = ∑ a i > a j   &   f i = = f j + 1 F j F_i=\sum_{a_i>a_j\ \& \ f_i==f_j+1} F_j Fi=ai>aj & fi==fj+1Fj
那么我们就可以维护一个 s u m i , j sum_{i,j} sumi,j 表示以 d p i , j dp_{i,j} dpi,j 为结尾的最长上升子序列方案数的前缀和。于是乎,我们同样可以通过一个二分完成转移。

int find(int x,int l,int r,vector<int> &a)
{
	int ans=0;
	while(l<=r)
	{
		int mid=l+r>>1;
		if(a[mid]>x)
		{
			ans=mid;
			l=mid+1;
		}
		else
		{
			r=mid-1;
		}
	}
	return ans;
}
void get(vector<int> a,vector<int> &f,vector<int> &F,int dec,int rev)
{
	if(rev)
	{
		reverse(a.begin(),a.end());
	}
	if(dec)
	{
		for(int i=0; i<a.size(); i++)
			a[i]=(int)a.size()-a[i]+1;
	}
	vector<vector<int>> pos,sum;
	vector<int> dp;
	f.assign(a.size(),0);
	F.assign(a.size(),0);
	for(int i=0; i<a.size(); i++)
	{
		int x=a[i];
		auto t=lower_bound(dp.begin(),dp.end(),x)-dp.begin();
		int c=t>0?(sum[t-1].back()-sum[t-1][find(a[i],0,pos[t-1].size()-1,pos[t-1])]):1;
		c=(c%mod+mod)%mod;
		if(t==dp.size())
		{
			dp.push_back(x);
			pos.push_back({inf,x});
			sum.push_back({0,c});
		}
		else
		{
			dp[t]=x;
			assert(pos.size()==dp.size()&&sum.size()==dp.size());
			pos[t].push_back(x);
			assert(sum[t].size());
			int p=sum[t].back();
			sum[t].push_back((p+c)%mod);
		}
		f[i]=t+1;
		F[i]=c;
	}
	if(rev)
	{
		reverse(f.begin(),f.end());
		reverse(F.begin(),F.end());
	}
}

题解

由于题目给的是一个排列,所以有一个比较显然的性质:
a n s = ( L I S + L D S − 1 )   o r   ( L I S + L D S ) ans=(LIS+LDS-1)\ or\ (LIS+LDS) ans=(LIS+LDS1) or (LIS+LDS)
因为每个数都是不一样的,所以 LIS 与 LDS 最多存在一个交点。
现在我们需要判断是否存在一种方式选出的 LIS 和 LDS 不存在交点。

我们可以先统计出 LIS 的个数 C 1 C1 C1 和 LDS 的个数 C 2 C2 C2,那么选择的方案数就是 r e s 1 = C 1 × C 2 res1=C1\times C2 res1=C1×C2
然后,我们再尝试统计经过 i i i 的 LIS 和 LDS H i H_i Hi,显然, r e s 2 = ∑ H i res2=\sum H_i res2=Hi 就是存在交点的 LIS 和 LDS 对数。
如果 r e s 1 = = r e s 2 res1==res2 res1==res2,那么所有 LIS 和 LDS 都存在交点,答案需要减一。

注意,这题卡自然溢出。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+7,inf=1e18,mod=1e9+7;
int find(int x,int l,int r,vector<int> &a)
{
	int ans=0;
	while(l<=r)
	{
		int mid=l+r>>1;
		if(a[mid]>x)
		{
			ans=mid;
			l=mid+1;
		}
		else
		{
			r=mid-1;
		}
	}
	return ans;
}
void get(vector<int> a,vector<int> &f,vector<int> &F,int dec,int rev)
{
	if(rev)
	{
		reverse(a.begin(),a.end());
	}
	if(dec)
	{
		for(int i=0; i<a.size(); i++)
			a[i]=(int)a.size()-a[i]+1;
	}
	vector<vector<int>> pos,sum;
	vector<int> dp;
	f.assign(a.size(),0);
	F.assign(a.size(),0);
	for(int i=0; i<a.size(); i++)
	{
		int x=a[i];
		auto t=lower_bound(dp.begin(),dp.end(),x)-dp.begin();
		int c=t>0?(sum[t-1].back()-sum[t-1][find(a[i],0,pos[t-1].size()-1,pos[t-1])]):1;
		c=(c%mod+mod)%mod;
		if(t==dp.size())
		{
			dp.push_back(x);
			pos.push_back({inf,x});
			sum.push_back({0,c});
		}
		else
		{
			dp[t]=x;
			assert(pos.size()==dp.size()&&sum.size()==dp.size());
			pos[t].push_back(x);
			assert(sum[t].size());
			int p=sum[t].back();
			sum[t].push_back((p+c)%mod);
		}
		f[i]=t+1;
		F[i]=c;
	}
	if(rev)
	{
		reverse(f.begin(),f.end());
		reverse(F.begin(),F.end());
	}
}
void O_o()
{
	int n;
	cin>>n;
	vector<int> a(n);
	for(int i=0; i<n; i++)
		cin>>a[i];
	vector<int> f,F;
	get(a,f,F,0,0);
	vector<int> g,G;
	get(a,g,G,1,0);
	vector<int> fd,Fd;
	get(a,fd,Fd,0,1);
	vector<int> gd,Gd;
	get(a,gd,Gd,1,1);
	
	int lis=0,lds=0;
	for(int i=0; i<n; i++)
	{
		lis=max(lis,f[i]);
		lds=max(lds,g[i]);
	}
	
	int c1=0,c2=0,res=0;
	for(int i=0; i<n; i++)
	{
		if(f[i]==lis)
			(c1+=F[i])%=mod;
		if(g[i]==lds)
			(c2+=G[i])%=mod;
	}
	for(int i=0; i<n; i++)
	{
		int ass=1;
		if(f[i]+gd[i]==lis+1)
			(ass*=F[i]*Gd[i]%mod)%=mod;
		else
			ass=0;
		if(g[i]+fd[i]==lds+1)
			(ass*=G[i]*Fd[i]%mod)%=mod;
		else
			ass=0;
		(res+=ass)%=mod;
	}
	int ans=lis+lds;
	if(res==c1*c2%mod)
		ans--;
	cout<<ans<<"\n";
}
signed main()
{
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	cout<<fixed<<setprecision(12);
	int T=1;
	cin>>T;
	while(T--)
	{
		O_o();
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值