【BZOJ 3157, 3516, 4126】 國王奇遇記 - 極致的組合數學

  恰好是去年的這個時候左右,我做了這個系列的前兩題。。(其實相當於只做了一題hhh)然而當時的姿勢水平非常低,式子大概都是瞎jb湊出來的。。。(也有可能看了波題解?)反正到了第三題就徹底一臉懵逼了。。記得看題解也看不懂是個毛。。後來就棄了。。
  一年之後的現在。。。前幾天dwj老司機誤以爲我做過這個題(的再加強版)。。於是就來問我怎麼證答案是用一個多項式來表示的。。。我想我好像都看(翻)完了具體數學求和的那幾章。。不如就來肛一波這個題啊。。。於是總共花了差不多三天,總時長大概5h+。。期間把我學過的不少組合數學的東西都搞過了。。獨立艹掉了這個題真是感動QAQ。。。然而感覺現在的姿勢水平還是好低啊。。(最後還是有一點同學的幫助hhhhh)
  
  進入正題啦。
  給定一個與n 無關的O(T(m)) 的算法,計算

k=1nkmmk

  比較套路的搞法是,因爲km 容易被表示,所以嘗試對這一項展開。於是稍微改變一下求和指標得到
  
k=0n1(k+1)mmk+1  =k=0n1j=0m(mj)kjmk+1  =j=0m(mj)mk=0n1kjmk

  可以看到後面那坨東西和一開始要算的東西形式類似,於是記
  
ft=k=1tktmk

  注意將fj 帶進去時要加上k==0 時的項,看起來像是0,但是實際上當j==0 時就會出問題。我們定義00=1 ,然後再加上這個1 就對了。於是變成
  
ft=mj=0t(tj)(fjnjmn+[j=0])  =mj=0t(tj)fjmn+1(n+1)t+1+m

  稍微移一下項可得
  
(m1)ft=mn+1ntmmj=0t1(tj)fj

  至此,我們得到了一個O(m2) 遞推的方法。

  接下來就不是那麼自然了,但也是可以想到的。
  注意到ft 的遞推式裏有一個mnnt ,這個非常重要,應該意識到,如果將ft 完全展開,可能會有一個mnPt(n) ,其中Pt(n) 是一個關於n 的多項式。
  因此我們先假設ft=mnPt(n)Qt(m) ,(不要問爲什麼是減)其中Qt(m) 是一個和n 無關的函數。
  則

(m1)ft=mn+1[(n+1)tj=0t1(tj)Pj(n)]+mj=0t1(tj)Qj(m)m

  對比係數得

Pt(n)=mm1[(n+1)tj=0t1(tj)Pj(n)]

Qt(m)=mm1[1j=0t1(tj)Qj(m)]

  注意到Pt(n) 的常數項形式和Qt(m) 的形式相同,並且恰好Qt(m)n 無關,而顯然Pt(n) 的確是一個關於nt 次多項式。因此有

fm=mnFm(n)Fm(0)

  其中Fm(n) 是一個m 次多項式。
  
  完結撒花
  
  然而知道這個有什麼卵用呢???
  如果我們將多項式換個表示方法,就會有神效。對於多項式Fm(n) ,考慮它的牛頓級數
Fm(n)=k=0n(nk)ck

  (實際上牛頓級數只用求和到m ,但是這裏爲了方便就求和到n )我們再來反演一波,有
  
cn=k=0n(nk)(1)nkFm(k)

  這實際上是cn=ΔnFm(0) ,所以順帶解釋了爲什麼牛頓級數只求和到m (對於k>mck ,它都是對m 次多項式Fm(n) 取了k 階差分後代入n=0 的值, 而取了k(k>m) 階差分後的m 次多項式顯然是0)。
  我們再將cn 帶回到原來的表達式裏,有
  
Fm(n)=k=0m(nk)j=0k(kj)(1)kjFm(j)  =j=0mFm(j)k=jm(nj)(njkj)(1)kj  =j=0mFm(j)(nj)k=0mj(njk)(1)k  =j=0m(nj)Fm(j)(1)mj(nj1mj)

  這意味着,知道Fm(0)...Fm(m) 的值就可以算出來任意一個Fm(n) 的值啦
  (解鎖成就:獨立推出O(m)Fm(n)
  UPD:原來這玩意就是傳說中的“線性插值”啊。。。23333333
  
  接下來就是算Fm(0)...Fm(m) 的問題了。
  我們來重新定義一下,設答案爲S(n)=n1k=1kmmk ,那麼有
  
S(n)=mnFm(n)Fm(0)

  對S(n) 取一次差分,可以得到Fm(n)Fm(n+1) 的關係:
  
nmmn=mn(mFm(n+1)Fm(n))

  也就是
  
Fm(n+1)=nm+Fm(n)m

  這樣可以遞推出Fm(1)...Fm(m) 的值。但是我們好像根本不知道Fm(0) 啊???
  其實這一步就簡單的了。。。考慮對S(n) 構造一個比較奇怪的m+1 階差分:
  
Δm+1S(n)mn  =k=0m+1(m+1k)(1)m+1k(Fm(n+k)Fm(0)mn+k)

  這個時候前面一半就是對Fm(n) 取了個m+1 差分,就消掉了。所以再稍微變一下,代n=0 就得到了
  
Fm(0)=m+1k=0(m+1k)(1)m+1kS(k)m+1k=0(m+1k)(1)mkmk

  S(0)...S(m) 顯然是可以直接算出來的。。。於是我們就可以算出來Fm(0)...Fm(m) 啦。。。
  接着利用前面的那個求值方法就可以算出Fm(n+1) ,然後就可以算出來S(n+1) 了。。。
  這樣就結束了。。。?

  我們來看一下複雜度。。。全程看起來指標都是O(m) 的很吼。。。
  。。。嗎?
  仔細看看會發現算S(m+1)=mk=1kmmk 的時候有mkm 哎。。。
  不管啦直接肛
  結果第一次交大常數版本的時候TLE了
  瞎改了點東西這破O(mlogm) 就跑到7s+了
  
  然後經同學提醒。。。這玩意是積性的啊。。。不是可以直接篩麼???
  [捂臉熊.jpg]
  於是就輕易地O(m) 啦。
  完結撒花!

【一些東西】
  不太會寫腳註就在這裏把中間用過的一些《具體數學》[人民郵電出版社,第二版]上的結論一次性列出來吧。
  n 階差分:P156,(5.40)
  牛頓級數:P157,沒有標成公式,畢竟只是多項式的一種表示形式吧。
  二項式反演:用了P160,(5.48)的另一種形式
  

f(n)=k(nk)g(k)g(n)=k(nk)(1)nkf(k)

  (nk)(kj)=(nj)(njkj) :P138,(5.21)。表5-4稱其爲”三項式版恆等式”。
  km(nk)(1)k=(1)m(n1m) :P136,(5.16)。帶交錯符號的二項式係數和。
  初始的式子還可以用分部求和搞出來,P234,(6.69)。

主要代碼

int n , m;
arr fact , invF , pw;
arr F , c2 , pwk;

void input() {
    n = rd() , m = rd();
}

inline void init() {
    m ++;
    fact[0] = invF[0] = 1;
    rep (i , 1 , m) fact[i] = mul(fact[i - 1] , i);
    invF[m] = Pow(fact[m] , mod - 2);
    per (i , m , 1) invF[i - 1] = mul(invF[i] , i);
    pw[0] = 1;
    m --;
    rep (i , 1 , m + 1) pw[i] = mul(pw[i - 1] , m);
    static arr vis , pr;
    int tot = 0;
    pwk[1] = 1;
    rep (i , 2 , m + 1) {
        if (!vis[i])
            pr[++ tot] = i , pwk[i] = Pow(i , m);
        rep (j , 1 , tot) if (i * pr[j] > m + 1) break; else {
            vis[i * pr[j]] = 1;
            pwk[i * pr[j]] = mul(pwk[i] , pwk[pr[j]]);
            if (i % pr[j] == 0) break;
        }
    }
}

inline int _C(int n , int m) {
    return n >= m ? mul(fact[n] , mul(invF[m] , invF[n - m])) : 0;
}

inline void get_F0() {
    int invm = Pow(m , mod - 2) , mm = 1;
    int up = 0 , down = 0;
    int S = 0;
    rep (k , 0 , m + 1) {

        int tmp = _C(m + 1 , k);
        if ((m + 1 - k) & 1) tmp = mod - tmp;
        tmp = mul(tmp , mm);

        up = add(up , mul(tmp , S));
        down = add(down , tmp);

        mm = mul(mm , invm);
        S = add(S , mul(pw[k] , pwk[k]));
    }
    up = mod - up;
    F[0] = mul(up , Pow(down , mod - 2));
}

inline void calc_F() {
    int invm = Pow(m , mod - 2);
    rep (k , 1 , m + 1) {
        F[k] = add(pwk[k - 1] , F[k - 1]);
        F[k] = mul(F[k] , invm);
    }
}

inline int binom(int n , int m) {
    int t = invF[m];
    For (i , 0 , m)
        t = mul(t , n - i);
    return t;
}

inline int inv(int i) {
    return mul(fact[i - 1] , invF[i]);
}

inline void calc_ans() {
    int ans = 0;
    int c1 = 1;
    c2[m] = 1;
    Dwn (j , m , 0) c2[j] = mul(c2[j + 1] , mul(n - j - 1 , inv(m - j)));
    rep (j , 0 , m) {
        if (j)
            c1 = mul(c1 , mul(n - j + 1 , inv(j)));

        int tmp = mul(c1 , F[j]);
        tmp = mul(tmp , c2[j]);
        if ((m - j) & 1) tmp = mod - tmp;
        ans = add(ans , tmp);
    }
    ans = mul(Pow(m , n) , ans);
    ans = dec(ans , F[0]);
    printf("%d\n" , ans);
}

void solve() {
    if (m == 1) {
        int ans = 1ll * n * (n + 1) / 2 % mod;
        printf("%d\n" , ans);
        return;
    }
    if (n <= m) {
        int ans = 0;
        int t = m;
        rep (i , 1 , n)
            ans = add(ans , mul(t , Pow(i , m))) , t = mul(t , m);
        printf("%d\n" , ans);
        return;
    }
    n ++;
    init();
    get_F0();
    calc_F();
    calc_ans();
}
發佈了135 篇原創文章 · 獲贊 6 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章