题解:排列

题意

定义一个序列是好的,当且仅当可以将序列分割成若干个子串,将每个子串翻转可以将序列排序。给定一个排列 \(a\),对于每个 \(1\le i\le n\),求 \(a\) 有多少个子序列是好的且不包含 \(a_i\),对 \(998244353\) 取模。

\(1\le n\le 3000\),1.5s。

题解

先考虑一整个排列怎么做。容易设计出一个 dp,\(dp_{i,j}\) 表示目前考虑到 \(i\),且目前的子序列最大值为 \(j\) 的好的子序列个数。

显然一个好的序列是由若干个单调递减子串组成,所以我们可以枚举最后一个子串的开头在哪,设它为 \(k\le i\),故转移为:

\[dp_{i,a_k}=C_{k,i}\sum\limits_{i'=1}^{k-1}\sum\limits_{j'=1}^{a_i-1}dp_{i',j'} \]

其中 \(C_{k,i}\) 表示 \(a_k,a_{k+1}\cdots a_i\) 中单调递减的子序列个数,这个容易使用树状数组动态求出,而后面那一坨可以使用二维前缀和维护,此时复杂度为 \(\mathcal O(n^2\log n)\)

直接把上面的做法套进原题就可以 \(\mathcal O(n^3\log n)\) 了,显然过不了,考虑优化。

注意到不包含 \(a_i\) 的好的子序列数量,等价于所有好的子序列数量减去包含 \(a_i\) 的子序列数量。

那如何求包含 \(a_i\) 的子序列数量呢?考虑枚举 \(a_i\) 所在的单调递减子序列的左端点 \(l\) 和右端点 \(r\)。注意到这需要我们知道前缀和后缀的 dp 信息,那我们先对前缀和后缀做一次 dp,设其分别为 \(dp\)\(rdp\)(注意此时 \(rdp\) 的第二维应为最小值),则子序列数量即为:

\[C_{l,i}\times C_{i,r}\times\sum\limits_{i'=1}^{l-1}\sum\limits_{j'=1}^{a_r-1}dp_{i',j'}\times\sum\limits_{i'=r+1}^n\sum\limits_{j'=a_l+1}^n rdp_{i',j'} \]

那我们直接枚举 \(i,l,r\),还是垃圾三次方,依旧过不了。

考虑优化。对于 \(i\),我们希望在枚举 \(l,r\) 的时候顺便求出 \(i\) 的答案。对于固定的 \(l\),如 \(a_i\) 在这个子序列中,那我们需要得知 \(C_{l,i}\) 的值,以及 \(i\) 到右端点的路径数乘上前后缀 dp 拼合的方案数。

对于 \(C_{l,i}\) 的值,我们依旧可以树状数组求。

对于 \(i\) 到右端点的路径数乘上前后缀 dp 拼合的方案数,我们分两种情况:

  • \(i\) 是右端点。方案数即为 \(\sum\limits_{i'=1}^{l-1}\sum\limits_{j'=1}^{a_i-1}dp_{i',j'}\times\sum\limits_{i'=i+1}^n\sum\limits_{j'=a_l+1}^n rdp_{i',j'}\),记为 \(X_i\)

  • \(i\) 不是右端点。那 \(i\) 会接到任意一个 \(k>i\)\(a_k<a_i\)\(k\)。此时的方案数之和为 \(X_k\) 的和,因为在这同时我们恰好隐式的算出了 \(C_{i,k}\) 的值对 \(X_k\) 的贡献(跟求递减子序列的原理相同)。

那我们从后往前枚举 \(i\),每次将 \(ans_i\) 加上第一种情况和第二种情况(即树状数组 \(1\)\(a_i-1\) 的和)的方案数之和乘上 \(C_{l,i}\) 的值,并将一、二种情况之和放进树状数组里。最终的答案就是 \(\sum dp-ans_i\)。这样总复杂度就是 \(\mathcal O(n^2\log n)\),轻松通过。

代码

int n,tr[3005],a[3005],dp[3005][3005],sum[3005][3005],rdp[3005][3005],rsum[3005][3005],ans[3005];
inline void update(int x,int y){for(;x<=n;x+=x&-x)cplus(tr[x],y);}
inline int query(int x){int r=0;for(;x;x-=x&-x)cplus(r,tr[x]);return r;}
int work(int n,int*a,int(*dp)[3005],int(*sum)[3005]){
	dp[0][0]=1;
	fo(i,0,n)sum[0][i]=1;
	int ret=0;
	fo(i,1,n){
		mem(tr,0),update(a[i],1);
		fd(j,i,1)if(a[j]>=a[i])
			dp[i][a[j]]=1ll*sum[j-1][a[i]-1]*query(a[j])%mod,
			update(a[j],query(a[j]-1));
		fo(j,0,n)sum[i][j]=fplus(fplus(sum[i-1][j],j?sum[i][j-1]:0),fminus(dp[i][j],j?sum[i-1][j-1]:0)),cplus(ret,dp[i][j]);
	}
	return ret;
}
void work_rev(int n,int*a,int(*dp)[3005],int(*sum)[3005]){
	dp[n+1][n+1]=1;
	fo(i,1,n+1)sum[n+1][i]=1;
	fd(i,n,1){
		mem(tr,0),update(a[i],1);
		fo(j,i,n)if(a[j]<=a[i])
			dp[i][a[j]]=1ll*sum[j+1][a[i]+1]*fminus(query(n),query(a[j]-1))%mod,
			update(a[j],fminus(query(n),query(a[j])));
		fd(j,n+1,1)sum[i][j]=fplus(fplus(sum[i+1][j],sum[i][j+1]),fminus(dp[i][j],sum[i+1][j+1]));
	}
}
int p[3005];
void solve(){
	cin>>n;
	fo(i,1,n)cin>>a[i];
	int tot=work(n,a,dp,sum);
	work_rev(n,a,rdp,rsum);
	fo(i,1,n){
		mem(tr,0),mem(p,0),update(a[i],p[i]=1);
		fo(j,i,n)if(a[j]<a[i])update(a[j],p[j]=fminus(query(n),query(a[j])));
		mem(tr,0);
		fd(j,n,i)if(a[j]<=a[i]){
			int tmp=fplus((int)(1ll*sum[i-1][a[j]-1]*rsum[j+1][a[i]+1]%mod),query(a[j]-1));
			cplus(ans[j],(int)(1ll*tmp*p[j]%mod)),update(a[j],tmp);
		}
	}
	fo(i,1,n)cout<<(tot-ans[i]+mod+1)%mod<<' ';
}
posted @ 2026-08-23 16:13  qwertim  阅读(3)  评论(0)    收藏  举报