题解:AT_arc213_d [ARC213D] Minimize Inversion

更好的阅读体验


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

线性做法还没学。


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

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

  • 若我们给 \(p_i\) 乘的系数是 \(1\),那么所有 \(j<i, p_j < p_i\) 的位置都会比 \(p_i\) 小,不产生贡献;所有 \(j>i, p_j < p_i\) 的位置都会比 \(p_i\) 小,都产生贡献。
  • 若我们给 \(p_i\) 乘的系数是 \(-1\),那么所有 \(j<i, p_j < p_i\) 的位置都会比 \(-p_i\) 大,都产生贡献;所有 \(j>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) \]

\(\displaystyle{\sum_{j=1}^{i} \min(j-1, i-j)}\) 可以拆成两个等差数列和的形式,可以表示为 \(\displaystyle{\left \lfloor \frac{i}{2}\right \rfloor\cdot \left \lfloor \frac{i-1}{2}\right \rfloor}\)

因此上式可以写成

\[\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)\)。存在线性做法,但是我不会。

#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;
}
posted @ 2026-07-02 17:44  dyc2022  阅读(3)  评论(0)    收藏  举报
/* 设置动态特效 */ /* 设置文章评论功能 */ 返回顶端 levels of contents