1. 程式人生 > 實用技巧 >loj 6077 「2017 山東一輪集訓 Day7」逆序對 題解

loj 6077 「2017 山東一輪集訓 Day7」逆序對 題解

loj 6077 「2017 山東一輪集訓 Day7」逆序對

題目傳送門

一個經典問題

我們一個一個加入元素,第i個貢獻的逆序對數量在區間\([0,i-1]\)

問題也就是有多少個排列\(x\)滿足:

\[\sum _{i}x_i=k\ |\ x_i\in [0,i-1] \]

可以考慮容斥:

如果有j個不滿足條件,也就是\(x_i\geq i\)

我們可以將不滿足條件的\(x_i\)都減去\(i\)

然後剩下的插板法分配。

對於一對\(i,j\)我們要知道有多少個排列\(p\)滿足:

\[\begin{align*} & (1). |p|=i\\ & (2). \sum p_i=j\\ & (3). \forall k\in[2,i],p_{k}>p_{k-1}\\ & (4). \forall k,p_{k}\in [1,n] \end {align*} \]

我們可以設\(dp_{i,j}\)表示滿足條件\((i,j)\)的方案數。

有兩種轉移:

  • 整體+1,\(dp_{i,j}\leftarrow dp_{i,j-i}\)
  • 整體+1,然後在開頭新增一個1:\(dp_{i,j}\leftarrow dp_{i-1,j-i}\)

這樣我們就可以滿足前三個限制了,第四個限制怎麼辦呢?

如果我們在恰好有一個元素\(>n\)的時候就將他減去,就會方便很多了。

則還有一個轉移:

  • \(dp_{i,j}\leftarrow -dp_{i-1,j-(n+1)}\)

可以發現\(i\leq \sqrt{n}\),所以時間複雜度為\(O(\sqrt n\times k)\)

/*
{
######################
#       Author       #
#        Gary        #
#        2020        #
######################
*/
#include <bits/stdc++.h>
#define rb(a,b,c) for(int a=b;a<=c;++a)
#define rl(a,b,c) for(int a=b;a>=c;--a)
#define LL long long
#define IT iterator
#define PB push_back
#define II(a,b) make_pair(a,b)
#define FIR first
#define SEC second
#define FREO freopen("check.out","w",stdout)
#define rep(a,b) for(int a=0;a<b;++a)
#define SRAND mt19937 rng(chrono::steady_clock::now().time_since_epoch().count())
#define random(a) rng()%a
#define ALL(a) a.begin(),a.end()
#define POB pop_back
#define ff fflush(stdout)
#define fastio ios::sync_with_stdio(false)
#define check_min(a,b) a=min(a,b)
#define check_max(a,b) a=max(a,b)
using namespace std;
//inline int read(){
//    int x=0;
//    char ch=getchar();
//    while(ch<'0'||ch>'9'){
//        ch=getchar();
//    }
//    while(ch>='0'&&ch<='9'){
//        x=(x<<1)+(x<<3)+(ch^48);
//        ch=getchar();
//    }
//    return x;
//}
const int INF = 0x3f3f3f3f;
typedef pair<int, int> mp;
/*}
*/
const int MOD = 1e9 + 7;
int n, k;
const int SQRT = 450;
const int MAXN = 1e5 + 20;
int dp[SQRT + 1][MAXN + 1]; //取了i個,和為j
int fact[MAXN + MAXN];
LL quick(LL A, LL B) {
    if (B == 0)
        return 1;

    LL  tmp = quick(A, B >> 1);
    tmp *= tmp;
    tmp %= MOD;

    if (B & 1)
        tmp *= A, tmp %= MOD;

    return tmp;
}
int inv(int x) {
    return quick(x, MOD - 2);
}
void add(int &A, int B) {
    A += B;

    if (A >= MOD)
        A -= MOD;
}
int c(int A, int B) {
    return 1ll * fact[A] * inv(fact[B]) % MOD * inv(fact[A - B]) % MOD;
}
int main() {
    scanf("%d%d", &n, &k);
    dp[0][0] = 1;
    rb(i, 1, SQRT) {
        rb(j, i, k) {
            dp[i][j] = (dp[i][j - i] + dp[i - 1][j - i]) % MOD;

            if (j > n) {
                add(dp[i][j], MOD - dp[i - 1][j - (n + 1)]);
            }
        }
    }
    fact[0] = 1;
    rb(i, 1, 200000) {
        fact[i] = 1ll * fact[i - 1] * i % MOD;
    }
    int rest = 0;
    rb(i, 0, k) {
        int tmp = c(k - i + n - 1, n - 1);
        rb(j, 0, SQRT) {
            int have = 1ll * tmp * dp[j][i] % MOD;

            if (j & 1) {
                have = MOD - have;
            }

            add(rest, have);
        }
    }

    cout << rest << endl;
    return 0;
}