题解:AT_arc213_d [ARC213D] Minimize Inversion

· · 题解

更好的阅读体验

感觉是很考验功底的组合计数题,可以说让人眼前一亮。

线性做法还没学。

我们先考虑对于一个排列 p,如何求出 f(p) 的值。

对于一个位置 i,我们只考虑 p_j < p_i 的位置和 p_i 产生的逆序对数量。

由此我们发现一个位置 i 产生的贡献只和 p_i 取正还是取负有关。因此假设 L_i 表示 [1, i)< p_i 的数字数量,R_i 表示 (i, n]< p_i 的数字数量。那么要最小化最终的逆序对数,答案就是

\sum_{i = 1}^n \min(L_i, R_i)

接下来考虑求出对于所有排列,这个东西的和。

为了简化问题,我们先考虑没有固定一个位置的情况下,如何求出所有 n! 个排列的 f(p) 之和。

那么我们可以把贡献拆到数字上,对于每个数字求出它的贡献。

那么假设我们要求 i 这个数字产生的贡献。显然序列中有恰好 i\le i 的数字。我们先不考虑这 i 个数字的内部顺序,那么有 \displaystyle {n \choose i} 种安排这 i 个数字位置的方案。接着我们枚举 1 \le j \le i,表示数字 i 在这 j 个数字中排名为 j。那么我们已经确定 i 的位置,<i 的数字有 (i-1)! 种可能的顺序,>i 的数字有 (n-i)! 种可能的顺序。最后,由于 i 左侧 <i 的数字有 j-1 个,右侧有 i-j 个,因此还要乘上 \min(j-1, i-j) 的系数。因此可以列出以下式子。

\sum_{i=1}^n {n \choose i} (i-1)! (n-i)! \sum_{j = 1}^n \min(j-1, i-j) 因此上式可以写成 $$ \sum_{i=1}^n {n \choose i} (i-1)! (n-i)! \left \lfloor \frac{i}{2}\right \rfloor \left \lfloor \frac{i-1}{2}\right \rfloor $$ 可以 $O(n)$ 求出。 *** 接下来考虑添加了一个 $p_k = x$ 的限制怎么做。这个时候全局的贡献分成三部分:对 $k$ 产生的贡献,$i$ 对 $j$ 产生的贡献($i, j \neq k$),$k$ 对 $i$ 产生的贡献($i \neq k$)。 首先考虑其他数对 $k$ 位置的 $\min(L_k, R_k)$ 产生的贡献。那么我们枚举 $k$ 左侧和右侧分别有几个 $< x$ 的数字。那么 $<x$ 的数字内部共有 $(x-1)!$ 种可能的顺序,$>x$ 的数字内部共有 $(n-x)!$ 种可能的顺序,在 $k$ 左侧安排 $i$ 个 $<x$ 的数字共有 $\displaystyle{k-1 \choose i}$ 种方案,在 $k$ 右侧安排 $j$ 个 $<x$ 的数字共有 $\displaystyle{n-k \choose j}$ 种方案。因此有式子 $$ (x-1)!(n-x)! \sum_{i + j = x-1}{k-1 \choose i} {n-k \choose j} \min(i,j) $$ 接下来考虑两个不为 $k$ 的位置之间产生的贡献。容易发现这种情况就相当于没有固定任何位置下,$n' = n-1$ 的情况。因此代入上面已经推得的式子,可得 $$ \sum_{i=1}^{n-1} {n-1 \choose i} (i-1)! (n-i-1)! \left \lfloor \frac{i}{2}\right \rfloor \left \lfloor \frac{i-1}{2}\right \rfloor $$ 接下来考虑本题最为复杂的部分:$k$ 对一个位置 $l$ 的贡献。 那么如果 $k$ 的插入能对 $L_l$ 或 $R_l$ 造成影响,那么一定要保证 $x < p_l$。那么我们记 $p_l = y$。 接下来,我们对 $l$ 相对于 $k$ 的位置展开讨论。显然如果 $l<k$,那么 $k$ 的插入会让 $R_l$ 增加 $1$。如果需要让 $\min(L_l, R_l)$ 增加,需保证 $L_l > R_l$。否则若 $l>k$,那么需保证 $L_l < R_l$。接下来假设 $k$ 左侧放置了 $i$ 个 $\le y$ 的数字,右侧放置了 $j$ 个 $\le y$ 的数字,$l$ 在所有 $\le y$ 的数字(不包含 $p_k$)中排名为 $t$。 - 若 $l<k$。那么 $L_l = t-1, R_l = j + i - t$。那么由 $L_l > R_l$,因此有 $\displaystyle {t > \frac{i+j+1}{2}}$。因此,若 $i>j$,则 $l$ 有 $\displaystyle {\frac{i-j}{2}}$ 种选择方案。否则,$l$ 有 $0$ 种选择方案。 - 若 $l>k$。同理,可以得到:若 $i < j$,则 $l$ 有 $\displaystyle {\frac{j-i}{2}}$。否则 $l$ 有 $0$ 种选择方案。 又因为安排 $< y$ 且 $\neq x$ 的数字总共有 $(y-2)!$ 种顺序,$> y$ 的数字有 $(n-y)!$ 种顺序。因此可以列出式子: $$ add_y = (y-2)!(n-y)! \sum_{i+j = y-1} \left \lfloor \frac{\left| i - j \right|}{2} \right \rfloor {k-1 \choose i} {n-k \choose j} $$ 现在考虑第一部分和第三部分的式子要怎么快速求出。 先考虑 $add_y$ 的求法。注意到,$|i - j| = i + j - 2 \cdot \min(i, j) = y - 1 - 2 \cdot \min(i, j)$。因此这个式子可以改写为 $$ add_y = (y-2)!(n-y)! \sum_{i+j = y-1} \left (\left \lfloor \frac{y - 1}{2} \right \rfloor - \min(i, j)\right ){k-1 \choose i} {n-k \choose j} $$ 接下来只需要考虑快速求出以下两个式子的值: $$ \sum_{i +j = C} {k - 1 \choose i} {n - k \choose j} \\ \sum_{i + j = C}{k - 1 \choose i}{n - k \choose j} \min(i, j) $$ 第一个式子是范德蒙德卷积,容易得到取值为 $\displaystyle{n - 1 \choose C}$。 第二个式子可以分治 NTT 求解。具体得,我们不妨先假设 $i<j$,那么就可以看作是 $\displaystyle{{k - 1 \choose i} \cdot i}$ 和 $\displaystyle{n-k \choose j}$ 这两个函数卷积,其中要求 $i < j$。至于 $i>j$ 的情况,倒过来重复这一过程即可。 最后,一个 $y$ 会对所有 $<y$ 的 $x$ 产生 $add_y$ 的贡献。这个用差分轻松维护。 那么这道题就做完了,时间复杂度为 $O(n \log^2 n)$。存在线性做法,但是我不会。 ```cpp #include<bits/stdc++.h> #define endl '\n' #define N 525006 using namespace std; using poly=vector<int>; constexpr int MOD=998244353,G=3,invG=332748118; int r[N]; int qpow(int x,int y) { int ret=1; for(;y;y>>=1,x=1ll*x*x%MOD)if(y&1)ret=1ll*ret*x%MOD; return ret; } void NTT(poly &a,int opt) { int len=a.size(); for(int i=0;i<len;i++) if(i<r[i])swap(a[i],a[r[i]]); for(int i=1;i<len;i<<=1) { int tmp=i<<1,Wn=qpow(opt==1?G:invG,(MOD-1)/tmp); for(int j=0;j<len;j+=tmp) { int w=1,x,y; for(int k=0;k<i;k++,w=1ll*w*Wn%MOD) { x=a[j+k],y=1ll*w*a[i+j+k]%MOD; a[j+k]=(x+y)%MOD,a[i+j+k]=(x+MOD-y)%MOD; } } } } poly operator +(poly a,poly b) { int n=a.size(),m=b.size(); poly ans(max(n,m)); for(int i=0;i<max(n,m);i++) { ans[i]=0; if(i<n)ans[i]=(ans[i]+a[i])%MOD; if(i<m)ans[i]=(ans[i]+b[i])%MOD; } return ans; } poly operator *(poly a,poly b) { int n=a.size(),m=b.size(),len=1,lg=0; while(len<n+m)len<<=1,lg++; while(a.size()<len)a.push_back(0); while(b.size()<len)b.push_back(0); for(int i=0;i<len;i++) r[i]=(r[i>>1]>>1)|((i&1)<<lg-1); NTT(a,1),NTT(b,1); for(int i=0;i<len;i++)a[i]=1ll*a[i]*b[i]%MOD; NTT(a,-1); int inv=qpow(len,MOD-2); poly ans(n+m-1); for(int i=0;i<n+m-1;i++)ans[i]=1ll*a[i]*inv%MOD; return ans; } poly& operator +=(poly &a,poly b) {return a=a+b;} poly& operator *=(poly &a,poly b) {return a=a*b;} int n,k,fac[N],ifac[N],ans[N],h[N]; poly f,g,res; inline void add(int &x,int y) {x+=y,x-=x>=MOD?MOD:0;} inline void dec(int &x,int y) {x+=MOD-y,x-=x>=MOD?MOD:0;} int binom(int x,int y) {return x<y?0:1ll*fac[x]*ifac[y]%MOD*ifac[x-y]%MOD;} void solve(int l,int r) { if(l==r)return; int mid=l+r>>1; poly x(mid-l+1),y(r-mid); for(int i=l;i<=mid;i++)x[i-l]=f[i]; for(int i=mid+1;i<=r;i++)y[i-mid-1]=g[i]; x*=y; int sz=x.size(); for(int i=0;i<sz;i++) if(l+mid+1+i<=n)add(res[l+mid+1+i],x[i]); solve(l,mid),solve(mid+1,r); } main() { scanf("%d%d",&n,&k),fac[0]=1; for(int i=1;i<N;i++)fac[i]=1ll*i*fac[i-1]%MOD; ifac[N-1]=qpow(fac[N-1],MOD-2); for(int i=N-2;~i;i--)ifac[i]=1ll*(i+1)*ifac[i+1]%MOD; f.resize(n+1),g.resize(n+1),res.resize(n+1); for(int &i:res)i=0; for(int i=0;i<=n;i++)f[i]=1ll*i*binom(k-1,i)%MOD; for(int i=0;i<=n;i++)g[i]=binom(n-k,i); solve(0,n); for(int i=0;i<=n;i++)f[i]=1ll*i*binom(n-k,i)%MOD; for(int i=0;i<=n;i++)g[i]=binom(k-1,i); solve(0,n); for(int i=0;i*2<=n;i++) add(res[i*2],1ll*binom(k-1,i)*binom(n-k,i)%MOD*i%MOD); for(int i=1;i<=n;i++) ans[i]=1ll*fac[i-1]*fac[n-i]%MOD*res[i-1]%MOD; int all=0; for(int i=1;i<n;i++) { int coef=1ll*binom(n-1,i)*fac[i-1]%MOD*fac[n-i-1]%MOD; coef=1ll*coef*(i>>1)%MOD; coef=1ll*coef*(i-1>>1)%MOD; add(all,coef); } for(int i=1;i<=n;i++)add(ans[i],all); for(int i=1;i<=n;i++) { int coef=1ll*(i-1>>1)*binom(n-1,i-1)%MOD; dec(coef,res[i-1]%MOD); coef=1ll*coef*fac[i-2]%MOD*fac[n-i]%MOD; add(h[1],coef),dec(h[i],coef); } for(int i=1;i<=n;i++)add(h[i],h[i-1]); for(int i=1;i<=n;i++) printf("%d\n",(ans[i]+h[i])%MOD); return 0; } ```