【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万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章