洛谷 P10254 口吃

zzafanti發表於2024-03-18

傳送門

description

求恰有 \(k\) 個逆序對的 \(n\) 的排列的權值和。權值為 \(\sum\limits_{i=1}^n p_i i\)

  • \(n\leq 300\)
  • \(k\leq \dbinom{n}{2}\)

solution

很重要的一個拆貢獻的想法是:一個數 \(p_i\) 的係數 \(i\) 拆成下標從 \(1\)\(i\) 的數中 \(p_j\ge p_i\)\(p_j<p_i\) 的兩部分。兩部分的權值和明顯是可以直接相加得到答案的。

考慮 dp 第一部分的貢獻。按照值域從大往小把 \(n\)\(1\) 插入排列,列舉插入位置即可算出逆序對和權值和的增加量。

具體地,設 \(f_{i,j}\) 表示把 \(n\)\(n-i+1\)\(i\) 個數插入排列,得到 \(j\) 和逆序對的方案數, \(g_{i,j}\) 表示權值和。

有轉移:

  • \(f_{i,j}=\sum\limits_{p=0}^{i-1} f_{i-1,j-p}\)
  • \(g_{i,j}=\sum\limits_{p=0}^{i-1} g_{i-1,j-p}+f_{i-1,j-p}*(n-i+1)*(p+1)\)

第二部分貢獻同理。

這樣就可以時間複雜度 \(O(n^2k)\) 做了。過不去。

整理一下式子可以發現可以字首和掉一部分轉移。

還有形如 \(\sum f_{i-1,j-p} \cdot p\) 的部分, 這個可以記字尾和陣列 \(s_{i}=\sum\limits_{p=i}^k f_{i-1,i} \cdot (k-i+1)\),結合上面的字首和就可以 \(O(1)\) 轉移了。

時間複雜度 \(O(nk)\)

hint

兩部分貢獻的 dp 只有 \(g\) 陣列轉移的一個係數不一樣,可以直接把係數加起來合併。

需要滾動陣列。

code

#include<bits/stdc++.h>

using namespace std;
using E=long long;
constexpr E mod=998244353;

int main(){

#ifdef zzafanti
  freopen("in.in","r",stdin);
#endif // zzafanti

  cin.tie(nullptr),cout.tie(nullptr)->sync_with_stdio(false);

  int n,k;
  cin>>n>>k;

  E ans=0;
  vector<vector<E>> f(2,vector<E>(k+1)),g(2,vector<E>(k+1)),sg(2,vector<E>(k+1)),sf(2,vector<E>(k+1));
  vector<E> trans(k+1);
  f[0][0]=1,g[0][0]=0;
  for(int j=0; j<=k; j++){
    sf[0][j]=1;
  }
  for(int i=1; i<=n; i++){
    for(int j=k; ~j; j--){
      trans[j]=(j==k?0:trans[j+1]);
      trans[j]=(trans[j]+f[i-1&1][j]*(k-j+1))%mod;
    }
    for(int j=0; j<=k; j++){
      f[i&1][j]=sf[i-1&1][j];
      if(j-i>=0) f[i&1][j]-=sf[i-1&1][j-i];
      E coef=n+i*i-2*i+1;
      g[i&1][j]=sg[i-1&1][j]+coef*sf[i-1&1][j]%mod;
      if(j-i>=0) g[i&1][j]-=sg[i-1&1][j-i]+coef*sf[i-1&1][j-i];
      coef=n-2*i+1;
      E dlt=(j-i+1>=0?trans[j-i+1]:trans[0])-(j<k?trans[j+1]:0)-(k-j)*1ll*(sf[i-1&1][j]-(j-i>=0?sf[i-1&1][j-i]:0));
      dlt%=mod;
      g[i&1][j]=(g[i&1][j]+coef*dlt)%mod;
      sf[i&1][j]=((j==0?0:sf[i&1][j-1])+f[i&1][j])%mod;
      sg[i&1][j]=((j==0?0:sg[i&1][j-1])+g[i&1][j])%mod;
    }
  }

  //cout<<(f[n&1][k]%mod+mod)%mod<<endl;
  cout<<(g[n&1][k]%mod+mod)%mod<<endl;

  return 0;
}