1. 程式人生 > 其它 >AtCoder ABC208 F - Cumulative Sum 題解

AtCoder ABC208 F - Cumulative Sum 題解

我禿了,我也變強了

寫在前面

昨天 ABC 的 F 題,結論推出來了,猜到是拉格朗日,奈何我只會板子,不會分析次數;

賽後知道正解後感覺大受震撼。我還是太 naive 了 /kk

前置知識

  • 組合數學

  • 拉格朗日插值法。

Description

原題地址

給你 \(n, m, k\),定義 \(f(n, m)\) 為:

\[\displaystyle f(n, m) = \begin{cases} 0 & (n = 0) \newline n^K & (n \gt 0, m = 0) \newline f(n-1, m) + f(n, m-1) & (n \gt 0, m \gt 0) \end{cases} \]

\(f(n,m) \bmod 10^9+7\)

資料範圍:

\(0 \le n \le 10^{18}\)

\(0 \le m \le 30\)

\(1 \le k \le 2.5 \times 10^{6}\)

Solution

先對第一個樣例搞一下。

我們用下面這個表格來算一下 \(f(3,4)\) 最終遞迴呼叫 \(f(i,0)\) 多少次

\[\begin{array}{c|lcr} & m = 0 & m = 1 & m = 2 & m = 3 & m = 4 \\ \hline n = 0 & 20 & 20 & 10 & 4 & 1\\ n = 1 & 10 & 10 & 6 & 3 & 1\\ n = 2 & 4 & 4 & 3 & 2 & 1\\ n = 3 & 1 & 1 & 1 & 1 & 1 \end{array} \]

結合 \(f(n, m) = f(n-1,m) + f(n, m-1) (n > 0, m > 0)\)

的遞迴式

發現這個玩意很像平面上 \((0,0)\)\((n,m)\) 的方案數。

而我們求的 \(f(i,0)\) 就對應著 \((i, 1)\)\((n, m)\) 的方案數。

根據組合數學,可以算出 \(f(i,0)\) 呼叫了 \(C_{n+m-i-1}^{m-1}\)

又因為 \(f(i,0) = i^k\)

然後我們就能夠用一個式子表示出答案:

\[\sum_{i = 0}^{n} \binom{n+m-i-1}{m-1} i^k \]

根據 @GuidingStar 的說法

字首和是 \(1\) 次, \(\binom{n+m-i-1}{m-1}\)\(m-1\) 次,\(i^k\)

\(k\) 次, 一共 \(k+m\) 次。

吾以為 之所以 \(\binom{n+m-i-1}{m-1}\)\(m-1\) 次,是因為這個式子約分之後分子與 \(i\) 相關的一共有 \(m-1\) 項,至於下面的 \((m-1)!\)\(i\) 無關。

我們設 \(F(n) = \sum_{i = 0}^{n} \binom{n+m-i-1}{m-1} i^k\)

這是一個 \(k+m\) 次多項式,我們只要找到 \(k+m+1\) 個點就能確定他的式子。

這裡我們取 \(x = 0 \sim k + m\) 這一段連續的區間,用暴力刷表的方法求出 \((x, F(x))\)

然後利用拉格朗日插值法求解即可。

用到 \(x\) 取值連續時的優化,如果不會可以參考這篇部落格

總複雜度為 \(O((k+m) \log k)\),可以通過。

Code

/*
Work by: Suzt_ilymics
Problem: 
Knowledge: 拉格朗日插值法
Time: O((k+m)log mod)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define int long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 5e6+5;
const int INF = 1e9+7;
const int mod = 1e9+7;

int n, m, k, Ans = 0;
int S[MAXN], fac[MAXN], inv[MAXN], pre[MAXN], suf[MAXN];
int ans[MAXN];

int read(){
    int s = 0, f = 0;
    char ch = getchar();
    while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
    while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
    return f ? -s : s;
}

int Pow(int x, int p, int mod) {
    int res = 1;
    while(p) {
        if(p & 1) res = res * x % mod;
        x = x * x % mod;
        p >>= 1;
    }
    return res;
}

signed main()
{
    n = read(), m  =read(), k = read();
    if(!m) {
        printf("%lld\n", Pow(n % mod, k, mod));
        return 0;
    }
    if(!n) {
        printf("0\n");
        return 0;
    }
    ans[0] = 0;
    for(int i = 1; i <= k + m; ++i) S[i] = Pow(i, k, mod);
    for(int i = 1; i <= m; ++i) {
        for(int j = 1; j <= k + m; ++j) {
            ans[j] = (ans[j - 1] + S[j]) % mod;
        }
        for(int j = 1; j <= k + m; ++j) {
            S[j] = ans[j];
        }
    }
    if(n <= k + m) {
        printf("%lld\n", ans[n]);
        return 0;
    }
//    for(int i = 1; i <= k + 2; ++i) inv[i] = Pow(fac[i], mod - 2, mod);
    pre[0] = n % mod, suf[k + m + 1] = 1, fac[0] = 1;
    for(int i = 1; i <= k + m; ++i) pre[i] = pre[i - 1] * ((n - i) % mod) % mod;
    for(int i = k + m; i >= 0; --i) suf[i] = suf[i + 1] * ((n - i) % mod) % mod;
    for(int i = 1; i <= k + m; ++i) fac[i] = fac[i - 1] * i % mod;
    for(int i = 0; i <= k + m; ++i) {
        int a = (i == 0 ? 1 : pre[i - 1]) * suf[i + 1] % mod;
        int b = fac[i] * ((k + m - i) & 1 ? -1 : 1) * fac[k + m - i] % mod;
        Ans = Ans + S[i] * a % mod * Pow(b, mod - 2, mod) % mod;
        Ans %= mod;
    }
    printf("%lld\n", (Ans + mod) % mod);
    return 0;
}