题解: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\) 的数字数量。那么要最小化最终的逆序对数,答案就是
接下来考虑求出对于所有排列,这个东西的和。
为了简化问题,我们先考虑没有固定一个位置的情况下,如何求出所有 \(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)\) 的系数。因此可以列出以下式子。
\(\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}\)。
因此上式可以写成
可以 \(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}\) 种方案。因此有式子
接下来考虑两个不为 \(k\) 的位置之间产生的贡献。容易发现这种情况就相当于没有固定任何位置下,\(n' = n-1\) 的情况。因此代入上面已经推得的式子,可得
接下来考虑本题最为复杂的部分:\(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\) 的求法。注意到,\(|i - j| = i + j - 2 \cdot \min(i, j) = y - 1 - 2 \cdot \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;
}

浙公网安备 33010602011771号