最近,小 S 对冒泡排序产生了浓厚的兴趣。为了问题简单,小 S 只研究对 *\(1\) 到 \(n\) 的排列*的冒泡排序。c++
下面是对冒泡排序的算法描述。算法
输入:一个长度为 n 的排列 p[1...n]数组
输出:p 排序后的结果。spa
for i = 1 to n docode
for j = 1 to n - 1 do排序
if(p[j] > p[j + 1])get
交换 p[j] 与 p[j + 1] 的值it
冒泡排序的交换次数被定义为交换过程的执行次数。能够证实交换次数的一个下界是 \(\frac 1 2 \sum_{i=1}^n \lvert i - p_i \rvert\),其中 \(p_i\) 是排列 \(p\) 中第 \(i\) 个位置的数字。若是你对证实感兴趣,能够看提示。class
小 S 开始专一于研究长度为 \(n\) 的排列中,知足交换次数 \(= \frac 1 2 \sum_{i=1}^n \lvert i - p_i \rvert\) 的排列(在后文中,为了方便,咱们把全部这样的排列叫「好」的排列)。他进一步想,这样的排列到底多很少?它们分布的密不密集?原理
小 S 想要对于一个给定的长度为 \(n\) 的排列 \(q\),计算字典序严格大于 \(q\) 的“好”的排列个数。可是他不会作,因而求助于你,但愿你帮他解决这个问题,考虑到答案可能会很大,所以只需输出答案对 \(998244353\) 取模的结果。
从文件 inverse.in
读入数据。
输入第一行包含一个正整数 \(T\),表示数据组数。
对于每组数据,第一行有一个正整数 \(n\),保证 \(n \leq 6 \times 10^5\)。
接下来一行会输入 \(n\) 个正整数,对应于题目描述中的 \(q_i\),保证输入的是一个 \(1\) 到 \(n\) 的排列。
输出到文件 inverse.out
中。
输出共 \(T\) 行,每行一个整数。
对于每组数据,输出一个整数,表示字典序严格大于 \(q\) 的「好」的排列个数对 \(998244353\) 取模的结果。
\(n\leq 600000 ,\sum n\leq 2000000\)
能够发现,若是一个序列合法,那么对于任意一个数,它在冒泡排序的过程当中只能最多向一边移动。咱们有知道,若是一个数的右边有比它小的数,那么它必定会向右移动,同理若是左边有一个比它大的数,那么也会向左移动。因此一个合法序列要求每一个数不能同时在两边都有逆序对。
根据这个原理就很容易获得一个状压的作法了。
而后考虑有没有多项式复杂度的作法。考虑从左往右填数。假设已经填了数中最大的是\(mx\),那么尚未填的数中比\(mx\)小的那一部分已经有了左边的逆序对,就不能有右边的逆序对了,因此这部分数应该保持升序,而比\(mx\)大的那部分则没有这个限制。
因而就能够\(DP\)了。设\(f_{i,j}\)表示还剩\(i\)个数没有填,没有限制的数有\(j\)个的方案数。
初始化:\(f_{0,0}=1\)。
转移:
\[ f_{i,j}=f_{i-1,j}+\sum_{k=0}^{j-1}f_{i-1,k}\\ =\sum_{k=0}^jf_{i-1,k} \]
考虑新加进来的数是否有限制,若是没有,那么知道他是第几个没有限制的数就能够知道剩下的没有限制的数还有多少个了。
这个\(DP\)方程是\(O(n^3)\)的,可是咱们也能够将这个方程写成如下形式:
\[ \begin{align} f_{i,j}&=f_{i,j-1}+f_{i-1,j}\\ f_{i,0}&=1 \end{align} \]
这样就变成\(O(n^2)\)的了。
而后考虑这个\(f_{n,m}\)的组合意义:一个网格上,从\((0,0)\)走到\((n,m)\),每步只能向上或者向右走,而且\(i\geq j\)的方案数。也就是说整个过程不能通过\(y=x+1\)这条直线。
因而就能够用一个叫折线定理的东西求解。求出\((0,0)\)对\(y=x+1\)的对称点\((-1,1)\),而后求出由\((-1,1)\)走到\((n,m)\)的方案数。\((-1,1)\)到\((n,m)\)的每条路径必定通过了\(y=x+1\)且惟一对应原问题的一个非法状况(将路径沿\(y=x+1\)翻折)。因此:
\[ f_{n,m}=\binom{n+m}{m}-\binom{n+m}{m-1} \]
考虑处理字典序的问题。咱们枚举\(k\),表示\(k\)以前的全部位置都与\(q_i\)相同,而后在\(k\)这个位置填的数\(>q_k\)。假设\(q_{1\ldots k}\)中最大的是\(q_{mx}\),在\(q_{mx+1\ldots n}\)中比\(q_{mx}\)大的有\(cnt\)个,则此时答案为\(f_{n-i+1,cnt-1}\)
咱们分状况讨论一下为何是这样的。咱们设\(k\)以前的最大值为\(mx\)
当序列已经不合法了就结束。
具体实现能够用树状数组。
代码:
#include<bits/stdc++.h> #define ll long long #define N 600005 using namespace std; inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;} const ll mod=998244353; ll ksm(ll t,ll x) { ll ans=1; for(;x;x>>=1,t=t*t%mod) if(x&1) ans=ans*t%mod; return ans; } int n; int q[N]; ll fac[N*2],ifac[N*2]; void pre(int n) { fac[0]=1; for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod; ifac[n]=ksm(fac[n],mod-2); for(int i=n-1;i>=0;i--) ifac[i]=ifac[i+1]*(i+1)%mod; } ll C(int n,int m) {return fac[n]*ifac[m]%mod*ifac[n-m]%mod;} ll cal(int n,int k) { if(k==0) return 1; if(k==1) return n; return (C(n+k,k)-C(n+k,k-1)+mod)%mod; } int low(int i) {return i&(-i);} int tem[N]; void add(int v,int f) {for(int i=v;i<=n;i+=low(i)) tem[i]+=f;} int query(int v) { int ans=0; for(int i=v;i;i-=low(i)) ans+=tem[i]; return ans; } bool vis[N]; int main() { pre(1200000); int T=Get(); while(T--) { ll ans=0; n=Get(); for(int i=1;i<=n;i++) tem[i]=0; for(int i=1;i<=n;i++) q[i]=Get(); int mx=0,cnt; for(int i=1;i<=n;i++) { if(q[i]>mx) { mx=q[i]; cnt=n-q[i]-(i-1-query(q[i]-1)); } (ans+=cal(n-i+1,cnt-1))%=mod; int low=query(q[i]-1); if(low!=i-1&&low!=q[i]-1) break; add(q[i],1); } cout<<ans<<"\n"; } return 0; }