题解:排列
题意
定义一个序列是好的,当且仅当可以将序列分割成若干个子串,将每个子串翻转可以将序列排序。给定一个排列 \(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\),故转移为:
其中 \(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\) 的第二维应为最小值),则子序列数量即为:
那我们直接枚举 \(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<<' ';
}

浙公网安备 33010602011771号