数学专题训练4 多项式

1. A*B Problem升级版

给你两个正整数 a , b a,b a,b,求 a × b a \times b a×b​。

1 ≤ a , b ≤ 1 0 6 1 \le a, b \le 10^6 1a,b106

#include
using namespace std;
const int N = 3000010;
typedef long long ll;
const ll mod = 998244353, g = 3;
char s1[N], s2[N];
ll a[N], b[N];
int tot, bit, rev[N];
ll mod_pow(ll x, ll n)
{
    ll res = 1;
    while(n)
    {
        if(n & 1) res = res * x % mod;
        x = x * x % mod;
        n >>= 1;
    }
    return res;
}

void ntt(ll a[], int inv)
{
    for(int i = 0; i < tot; i++)
    {
        if(i < rev[i]) swap(a[i], a[rev[i]]);
    }
    for(int mid = 1; mid < tot; mid <<= 1)
    {
        ll w1 = mod_pow(g, (mod - 1) / (2 * mid));
        if(inv == -1) w1 = mod_pow(w1, mod - 2);
        for(int i = 0; i < tot; i += 2 * mid)
        {
            ll wk = 1;
            for(int j = 0; j < mid; j++, wk = wk * w1 % mod)
            {
                ll x = a[i + j], y = wk * a[i + j + mid] % mod;
                a[i + j] = (x + y) % mod, a[i + j + mid] = (x - y + mod) % mod;
            }
        }
    }
}

int main()
{
    scanf("%s%s", s1, s2);
    int n = strlen(s1) - 1, m = strlen(s2) - 1;

    for(int i = 0; i <= n; i++) a[i] = s1[n - i] - '0';
    for(int i = 0; i <= m; i++) b[i] = s2[m - i] - '0';

    while((1 << bit) < n + m + 1) bit++;
    tot = 1 << bit;

    for(int i = 0; i < tot; i++)
    {
        rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (bit - 1));
    }
    ntt(a, 1), ntt(b, 1);
    for(int i = 0; i < tot; i++) a[i] = a[i] * b[i] % mod;
    ntt(a, -1);

    ll inv = mod_pow(tot, mod - 2);
    for(int i = 0; i <= n + m; i++) a[i] = a[i] * inv % mod;
    ll tmp = 0;
    int id;
    for(id = 0;; id++)
    {
        if(id >= tot && !tmp) break;
        tmp += a[id], a[id] = tmp % 10;
        tmp /= 10;
    }
    while(id && a[id] == 0) id--;
    for(int i = id; i >= 0; i--) printf("%lld", a[i]);

    return 0;
}

2. 力

n n n 个数字 q 1 , q 2 , . . . , q n q_1,q_2,...,q_n q1,q2,...,qn,定义
F j = ∑ i = 1 j − 1 q i × q j ( i − j ) 2 − ∑ i = j + 1 n q i × q j ( i − j ) 2 E i = F i q i F_j = \sum\limits_{i=1}^{j-1}\frac{q_i \times q_j}{(i-j)^2} - \sum\limits_{i = j + 1}^{n}\frac{q_i \times q_j}{(i-j)^2}\\ E_i = \frac{F_i}{q_i} Fj=i=1j1(ij)2qi×qji=j+1n(ij)2qi×qjEi=qiFi
E 1 , E 2 , . . . , E n E_1,E_2,...,E_n E1,E2,...,En​​.

f [ i ] = q i , g [ i ] = 1 i 2 f[i]=q_i,g[i]=\frac{1}{i^2} f[i]=qi,g[i]=i21 ,所以原式变成

∑ i = 1 j f [ i ] ∗ g [ j − i ] − ∑ i = j n f [ i ] ∗ g [ i − j ] \sum_{i=1}^{j}f[i]*g[j-i]-\sum_{i=j}^{n}f[i]*g[i-j] i=1jf[i]g[ji]i=jnf[i]g[ij]

f [ 0 ] = 0 , g [ 0 ] = 0 f[0]=0,g[0]=0 f[0]=0,g[0]=0,则原式变成

∑ i = 0 j f [ i ] ∗ g [ j − i ] − ∑ i = j n f [ i ] ∗ g [ i − j ] \sum_{i=0}^{j}f[i]*g[j-i]-\sum_{i=j}^{n}f[i]*g[i-j] i=0jf[i]g[ji]i=jnf[i]g[ij]

这时我们发现,左边已经是一个卷积的形式,所以我们直接来推右边

∑ i = j n f [ i ] ∗ g [ i − j ] \sum_{i=j}^{n}f[i]*g[i-j] i=jnf[i]g[ij] 展开,发现: f [ j ] ∗ g [ 0 ] + f [ j + 1 ] ∗ g [ 1 ] + . . . + f [ j + ( n − j ) ] ∗ g [ n − j ] f[j]*g[0]+f[j+1]*g[1]+...+f[j+(n-j)]*g[n-j] f[j]g[0]+f[j+1]g[1]+...+f[j+(nj)]g[nj]

所以我们可以将原式写成: ∑ i = 0 n − j f [ j + i ] ∗ g [ i ] \sum_{i=0}^{n-j}f[j+i]*g[i] i=0njf[j+i]g[i]

引入 f ′ [ i ] = f [ n − i ] f'[i]=f[n-i] f[i]=f[ni]​ ,实际上这是一种翻转的套路。则原式可写为: ∑ i = 0 n − j f ′ [ n − ( j + i ) ] ∗ g [ i ] \sum_{i=0}^{n-j}f'[n-(j+i)]*g[i] i=0njf[n(j+i)]g[i]

∑ i = 0 n − j f ′ [ n − j − i ] ∗ g [ i ] \sum_{i=0}^{n-j}f'[n-j-i]*g[i] i=0njf[nji]g[i]

t = n − j t = n-j t=nj,则原式等于
∑ i = 0 t f ′ [ t − i ] ∗ g [ i ] \sum_{i=0}^{t}f'[t-i]*g[i] i=0tf[ti]g[i]

E j = ∑ i = 0 j f [ i ] ∗ g [ j − i ] − ∑ i = 0 t f ′ [ t − i ] ∗ g [ i ] E_j=\sum_{i=0}^{j}f[i]*g[j-i]-\sum_{i=0}^{t}f'[t-i]*g[i] Ej=i=0jf[i]g[ji]i=0tf[ti]g[i]​​

#include
using namespace std;
const int N = 400010;
const double PI = acos(-1);
struct Complex
{
    double x, y;
    Complex(double x_, double y_) : x(x_), y(y_){}
    Complex(){}
    Complex operator + (Complex& c)
    {
        return Complex(x + c.x, y + c.y);
    }
    Complex operator - (Complex& c)
    {
        return Complex(x - c.x, y - c.y);
    }
    Complex operator * (Complex& c)
    {
        return Complex(x * c.x - y * c.y, x * c.y + y * c.x);
    }
};
Complex a[N], b[N], c[N];

int tot, bit, rev[N];

void fft(Complex a[], int inv)
{
    for(int i = 0; i < tot; i++)
    {
        if(i < rev[i]) swap(a[i], a[rev[i]]);
    }
    for(int mid = 1; mid < tot; mid <<= 1)
    {
        Complex w1(cos(PI / mid), inv * sin(PI / mid));
        for(int i = 0; i < tot; i += mid * 2)
        {
            Complex wk(1, 0);
            for(int j = 0; j < mid; j++, wk = wk * w1)
            {
                auto x = a[i + j], y = wk * a[i + j + mid];
                a[i + j] = x + y, a[i + j + mid] = x - y;
            }
        }
    }
}
int main()
{
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
    {
        scanf("%lf", &a[i].x);
        b[i].x = 1.0 / i / i;
    }
    for(int i = 0; i <= n; i++) c[i].x = a[n - i].x;

    while((1 << bit) <= n + n) bit++;
    tot = 1 << bit;
    for(int i = 0; i < tot; i++)
    {
        rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (bit - 1));
    }
    fft(a, 1), fft(b, 1), fft(c, 1);
    for(int i = 0; i < tot; i++)
    {
        a[i] = a[i] * b[i], c[i] = c[i] * b[i];
    }
    fft(a, -1), fft(c, -1);
    for(int i = 1; i <= n; i++)
    {
        printf("%.5f\n", (a[i].x - c[n - i].x) / tot);
    }
    return 0;
}

3. 礼物

给两个序列 1 ≤ n ≤ 50000 , 1 ≤ a i , b i ≤ 100 1 \le n \le 50000, 1 \le a_i,b_i \le 100 1n50000,1ai,bi100,可以对其中一个序列的所有元素加上一个值 c c c c c c 可以是负数),然后把其中一个序列想象成一个环,可以旋转。求 ∑ i = 1 n ( a i − b i ) 2 \sum\limits_{i = 1}^n(a_i - b_i)^2 i=1n(aibi)2 的最小值.

我们令增加量为 x x x​,旋转以后的原数列为 { a } { b } \{a\}\{b\} {a}{b}​那么现在的费用就是:

∑ i = 1 n ( a i + x − b i ) 2 \sum_{i=1}^n\left(a_i+x-b_i\right)^2 i=1n(ai+xbi)2

我们把第i项拿出来拆开,得到:

( a i + x − b i ) 2 = a i 2 + b i 2 + x 2 + 2 a i x − 2 a i b i − 2 b i x \left(a_i+x-b_i\right)^2=a_i^2+b_i^2+x^2+2a_ix-2a_ib_i-2b_ix (ai+xbi)2=ai2+bi2+x2+2aix2aibi2bix​​​

∑ i = 1 n a i 2 + ∑ i = 1 n b i 2 + n x 2 + 2 x ( ∑ i = 1 n a i − ∑ i = 1 n b i ) − 2 ∑ i = 1 n a i b i \sum_{i=1}^na_i^2+\sum_{i=1}^nb_i^2+nx^2+2x\left(\sum_{i=1}^na_i-\sum_{i=1}^nb_i\right)-2\sum_{i=1}^na_ib_i i=1nai2+i=1nbi2+nx2+2x(i=1naii=1nbi)2i=1naibi

那么我们只要令最后一项最大,那么就可以得到最小的费用值了

现在问题转化为求 ∑ i = 1 n a i b i \sum_{i=1}^na_ib_i i=1naibi​​的最大值

我们把数列 { a } \{a\} {a}反过来,变成

∑ i = 1 n a n − i + 1 b i \sum_{i=1}^na_{n-i+1}b_i i=1nani+1bi

所以把反过来的数列 { a } \{a\} {a}倍长,和数列 { b } \{b\} {b}卷积,得到的项里面的第 n + 1 n+1 n+1 n ∗ 2 n*2 n2 项的最大值,就是 ∑ i = 1 n a i b i \sum_{i=1}^na_ib_i i=1naibi​​​的最大值. 然后把前面的不变项加上,就是答案了.

#include
using namespace std;
const int N =300010;
typedef long long ll;
const ll mod = 998244353, g = 3;
ll mod_pow(ll x, ll n)
{
    ll res = 1;
    while(n)
    {
        if(n & 1) res = res * x % mod;
        x = x * x % mod;
        n >>= 1;
    }
    return res;
}
ll a[N], b[N];
int tot, bit, rev[N];
void ntt(ll a[], int inv)
{
    for(int i = 0; i < tot; i++)
    {
        if(i < rev[i]) swap(a[i], a[rev[i]]);
    }
    for(int mid = 1; mid < tot; mid <<= 1)
    {
        ll w1 = mod_pow(g, (mod - 1) / (2 * mid));
        if(inv == -1) w1 = mod_pow(w1, mod - 2);
        for(int i = 0; i < tot; i += 2 * mid)
        {
            ll wk = 1;
            for(int j = 0; j < mid; j++, wk = wk * w1 % mod)
            {
                ll x = a[i + j], y = wk * a[i + j + mid] % mod;
                a[i + j] = (x + y) % mod, a[i + j + mid] = (x - y + mod) % mod;
            }
        }
    }
}
ll A, B, C;
ll f(ll x)
{
    return A * x * x + B * x + C;
}
int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
    {
        scanf("%lld", &a[i]);
        C += a[i] * a[i];
        B += a[i];
        a[n + i] = a[i];
    }
    for(int i = 1; i <= n; i++)
    {
        scanf("%lld", &b[i]);
        C += b[i] * b[i];
        B -= b[i];
    }
    reverse(b + 1, b + n + 1);

    while((1 << bit) <= 3 * n) bit++;
    tot = 1 << bit;
    for(int i = 0; i < tot; i++)
    {
        rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (bit - 1));
    }

    ntt(a, 1), ntt(b, 1);
    for(int i = 0; i < tot; i++)
    {
        a[i] = a[i] * b[i] % mod;
    }

    ntt(a, -1);
    ll res = 0, inv = mod_pow(tot, mod - 2);
    for(int i = n + 1; i <= 2 * n; i++)
    {
        res = max(res, a[i] * inv % mod);
    }

    A = n, B *= 2, C -= 2 * res;
    //二次函数求最值一定要小心,因为负数的向上取整是/ ,而正数的向下取整是/ ,因此要把这对称轴附近的三个值都取一遍.
    
    ll ans = min(f(-B / (2 * A) - 1), f(-B / (2 * A)));
    ans = min(ans, f(-B / (2 * A) + 1));
    printf("%lld\n", ans);

    return 0;
}

4. 差分与前缀和

给定一个长为 n n n 的序列 a a a,求出其 k k k 阶差分或前缀和。 1 ≤ n ≤ 1 0 5 , 1 ≤ a i ≤ 1 0 9 , k ≤ 1 0 2333 1 \le n \le 10^5, 1 \le a_i \le 10^9, k \le 10^{2333} 1n105,1ai109,k102333.
结果的每一项都需要对 1004535809 1004535809 1004535809​ 取模。

可以看作 g = f ∗ a g = f * a g=fa,其中 a a a​ 是输入的数组, f i = { C k + i − 1 k , t = 0 C k i − 1 , t = 1 f_i = \begin{cases}C_{k+i-1}^k, & t = 0 \\ C_k^{i - 1}, & t = 1\end{cases} fi={Ck+i1k,Cki1,t=0t=1

i i i 项答案就是卷积的第 i + 1 i + 1 i+1 项的系数.

#include
using namespace std;
typedef long long ll;
const ll mod = 1004535809, g = 3;
const int N = 300010;
ll mod_pow(ll x, ll n)
{
    ll res = 1;
    while(n)
    {
        if(n & 1) res = res * x % mod;
        x = x * x % mod;
        n >>= 1;
    }
    return res;
}
char str[N];
int tot, bit, rev[N];
ll a[N], f[N];

void ntt(ll a[], int inv)
{
    for(int i = 0; i < tot; i++)
    {
        if(i < rev[i]) swap(a[i], a[rev[i]]);
    }

    for(int mid = 1; mid < tot; mid <<= 1)
    {
        ll w1 = mod_pow(g, (mod - 1) / (2 * mid));
        if(inv == -1) w1 = mod_pow(w1, mod - 2);
        for(int i = 0; i < tot; i += 2 * mid)
        {
            ll wk = 1;
            for(int j = 0; j < mid; j++, wk = wk * w1 % mod)
            {
                ll x = a[i + j], y = wk * a[i + j + mid] % mod;
                a[i + j] =(x + y) % mod, a[i + j + mid] = (x - y + mod) % mod;
            }
        }
    }
}

int main()
{
    int n, t;
    scanf("%d", &n);
    scanf("%s", str + 1);
    scanf("%d", &t);
    for(int i = 1; i <= n; i++) scanf("%lld", &a[i]);

    ll tmp = 0;
    for(int i = 1; str[i]; i++)
    {
        tmp = (tmp * 10 + str[i] - '0') % mod;
    }
    ll k = tmp;

    f[1] = 1;
    if(t == 0) for(int i = 2; i <= n; i++)
    {
        f[i] = f[i - 1] * (k + i - 2) % mod * mod_pow(i - 1, mod - 2) % mod;
    }

    else
    {
        for(int i = 2; i <= n; i++)
            f[i] = f[i - 1] * (k - i + 2) % mod * mod_pow(i - 1, mod - 2) % mod;
        for(int i = 2; i <= n; i += 2)
            f[i] = mod - f[i];
    }
    
    while((1 << bit) <= n + n) bit++;
    tot = 1 << bit;
    for(int i = 0; i < tot; i++)
    {
        rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (bit - 1));
    }
    ntt(a, 1), ntt(f, 1);
    for(int i = 0; i < tot; i++) a[i] = a[i] * f[i] % mod;
    ntt(a, -1);
    ll inv = mod_pow(tot, mod - 2);
    for(int i = 1; i <= n; i++)
    {
        printf("%lld ", a[i + 1] * inv % mod);
    }
    return 0;
}

5. 3-idiots

6. 【模板】分治 FFT

给定序列 g 1 … n − 1 g_{1\dots n - 1} g1n1,求序列 f 0 … n − 1 f_{0\dots n - 1} f0n1

其中 f i = ∑ j = 1 i f i − j g j f_i=\sum_{j=1}^if_{i-j}g_j fi=j=1ifijgj,边界为 f 0 = 1 f_0=1 f0=1

答案对 998244353 998244353 998244353 取模。

不妨设 F ( x ) = ∑ i = 0 ∞ f i x i , G ( x ) = ∑ i = 0 ∞ g i x i F(x)=\sum_{i=0}^{\infty}f_ix^i,G(x)=\sum_{i=0}^{\infty}g_ix^i F(x)=i=0fixi,G(x)=i=0gixi​,且 g 0 = 0 g_0=0 g0=0

那么有 F ( x ) G ( x ) = ∑ i = 0 ∞ x i ∑ j + k = i f j g k = F ( x ) − f 0 x 0 F(x)G(x)=\sum_{i=0}^{\infty}x^i\sum_{j+k=i}f_jg_k=F(x)-f_0x^0 F(x)G(x)=i=0xij+k=ifjgk=F(x)f0x0

F ( x ) G ( x ) ≡ F ( x ) − f 0 (   m o d   x n ) F(x)G(x) \equiv F(x)-f_0 (\bmod x^n) F(x)G(x)F(x)f0(modxn)

F ( x ) ≡ f 0 1 − G ( x ) (   m o d   x n ) F(x) \equiv \dfrac{f_0}{1-G(x)} (\bmod x^n) F(x)1G(x)f0(modxn)

F ( X ) ≡ ( 1 − G ( x ) ) − 1 (   m o d   x n ) F(X) \equiv (1-G(x))^{-1} (\bmod x^n) F(X)(1G(x))1(modxn)

#include
using namespace std;
typedef long long ll;
const ll mod = 998244353, g = 3;
const int N = 300010;
ll a[N], b[N], c[N];
int rev[N];

ll mod_pow(ll x, ll n)
{
    ll res = 1;
    while(n)
    {
        if(n & 1) res = res * x % mod;
        x = x * x % mod;
        n >>= 1;
    }
    return res;
}

void ntt(ll a[], int tot, int type)
{
    for(int i = 0; i < tot; i++)
    {
        if(i < rev[i]) swap(a[i], a[rev[i]]);
    }

    for(int mid = 1; mid < tot; mid <<= 1)
    {
        ll w1 = mod_pow(g, (mod - 1) / (2 * mid));
        if(type == -1) w1 = mod_pow(w1, mod - 2);
        for(int i = 0; i < tot; i += 2 * mid)
        {
            ll wk = 1;
            for(int j = 0; j < mid; j++, wk = wk * w1 % mod)
            {
                ll x = a[i + j], y = wk * a[i + j + mid] % mod;
                a[i + j] = (x + y) % mod, a[i + j + mid] = (x - y + mod) % mod;
            }
        }
    }
    if(type == -1)
    {
        ll inv = mod_pow(tot, mod - 2);
        for(int i = 0; i < tot; i++) a[i] = a[i] * inv % mod;
    }
}

void polyinv(int n)
{
    b[0] = mod_pow(a[0], mod - 2);

    for(int t = 2; t <= 2 * n; t <<= 1)
    {
        int t2 = t << 1;
        int bit = 0;
        while((1 << bit) < t2) bit++;
        for(int i = 0; i < t2; i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (bit - 1));

        copy(a, a + t, c);

        ntt(b, t2, 1), ntt(c, t2, 1);
        for(int i = 0; i < t2; i++)
        {
            b[i] = b[i] * (2 - b[i] * c[i] % mod + mod) % mod;
        }
        ntt(b, t2, -1);
        fill(b + t, b + t2, 0);
    }
}

int main()
{
    int n;
    scanf("%d", &n);
    a[0] = 1;
    for(int i = 1; i < n; i++)
    {
        scanf("%lld", &a[i]);
        a[i] = a[i] ? mod - a[i] : 0;
    }
    polyinv(n);
    for(int i = 0; i < n; i++) printf("%lld ", b[i]);
    return 0;
}

7. H-Hash Function_2021牛客暑期多校训练营1 (nowcoder.com)

给定一个大小为 n ( 1 ≤ n ≤ 5 × 1 0 5 ) n(1\leq n \leq 5 \times10^5) n(1n5×105)​ 的集合 S = { a 0 , a 1 , . . . , a n − 1 } S=\{a_0,a_1,...,a_{n-1}\} S={a0,a1,...,an1}​. 求最小的正整数 s e e d seed seed,使得对于任意 i i i ( 0 ≤ i ≤ n ) (0\leq i \leq n) (0in), a i ( m o d s e e d ) a_i \pmod {seed} ai(modseed)​​ 的结果各不相同。

若存在 a i ≡ a j ( m o d s e e d ) a_i \equiv a_j \pmod {seed} aiaj(modseed),则 a i − a j ≡ 0 ( m o d s e e d ) a_i - a_j \equiv 0\pmod {seed} aiaj0(modseed)。这说明若 s e e d seed seed 不是合法解,则存在 a i − a j a_i - a_j aiaj s e e d seed seed 的倍数

换句话说,如果我们知道了所有 a i − a j a_i - a_j aiaj 的值,就可以用枚举因数的方法找到最小的 s e e d seed seed.

如果求 a i − a j a_i - a_j aiaj 的值呢?并不好求,但是我们发现可以用卷积的方法快速求出 a i + a j a_i + a_j ai+aj 的值。因此我们可以令 b i = 500000 − a i b_i = 500000 - a_i bi=500000ai​. 然后两个多项式卷积即可.

注意最小的 s e e d seed seed 可能是 500001 500001 500001.

#include
using namespace std;
const int N = 2000010;
typedef long long ll;
const ll mod = 998244353, g = 3;

ll mod_pow(ll x, ll n)
{
    ll res = 1;
    while(n)
    {
        if(n & 1) res = res * x % mod;
        x = x * x % mod;
        n >>= 1;
    }
    return res;
}

ll a[N], b[N];
int rev[N], tot, bit;

void ntt(ll a[], int tot, int type)
{
    for(int i = 0; i < tot; i++)
    {
        if(i < rev[i]) swap(a[i], a[rev[i]]);
    }

    for(int mid = 1; mid < tot; mid <<= 1)
    {
        ll w1 = mod_pow(g, (mod - 1) / (2 * mid));
        if(type == -1) w1 = mod_pow(w1, mod - 2);
        for(int i = 0; i < tot; i += 2 * mid)
        {
            ll wk = 1;
            for(int j = 0; j < mid; j++, wk = wk * w1 % mod)
            {
                ll x = a[i + j], y = wk * a[i + j + mid] % mod;
                a[i + j] = (x + y) % mod, a[i + j + mid] = (x - y + mod) % mod;
            }
        }
    }

    if(type == -1)
    {
        ll inv = mod_pow(tot, mod - 2);
        for(int i = 0; i < tot; i++)
        {
            a[i] = a[i] * inv % mod;
        }
    }
}
int main()
{
    int n;
    scanf("%d", &n);
    int m = 500000;

    for(int i = 1; i <= n; i++)
    {
        int x;
        scanf("%d", &x);
        a[x] = b[m - x] = 1;
    }

    while((1 << bit) <= 2 * m + 1) bit++;
    tot = 1 << bit;

    for(int i = 0; i < tot; i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (bit - 1));

    ntt(a, tot, 1), ntt(b, tot, 1);
    for(int i = 0; i < tot; i++) a[i] = a[i] * b[i] % mod;
    ntt(a, tot, -1);

    int ans = -1;
    for(int i = 1; i <= m; i++)
    {
        bool flag = false;
        for(int j = i; j <= m; j += i)
        {
            if(a[j + m])
            {
                flag = true;
                break;
            }
        }
        if(!flag)
        {
            abs = i;
            break;
        }
    }
    if(ans == -1) ans = m + 1;
    printf("%d\n", ans);
    return 0;
}

8. H-Convolution_2021牛客暑期多校训练营4 (nowcoder.com)

题意
给定 ( n , k , D ) (n,k,D) (n,k,D)​​​,求

∑ a i ≥ 0 , ∑ i = 1 n a i = D D ! ∏ i = 1 n ( a i + k ) ! \sum_{a_i\ge0,\sum_{i=1}^n a_i=D}\dfrac{D!}{\prod_{i=1}^n(a_i+k)!} ai0,i=1nai=Di=1n(ai+k)!D!

1 ≤ n ≤ 50 , 0 ≤ k ≤ 50 , 0 ≤ D ≤ 1 0 8 1\le n\le 50,0\le k\le 50,0\le D\le 10^8 1n50,0k50,0D108​​​

由于 e x = ∑ k = 0 ∞ x k k ! e^x=\sum_{k=0}^{\infty}\dfrac{x^k}{k!} ex=k=0k!xk​​​​​​​​​​,所以有

∑ a i ≥ 0 , ∑ i = 1 n a i = D 1 ∏ i = 1 n a i ! = [ x D ] ( e x ) n = [ x D ] e n x \sum_{a_i\ge0,\sum_{i=1}^n a_i=D}\dfrac{1}{\prod_{i=1}^na_i!}=[x^D](e^x)^n=[x^D]e^{nx} ai0,i=1nai=Di=1nai!1=[xD](ex)n=[xD]enx​​​​​​​​​

b i = a i + k b_i=a_i+k bi=ai+k​​​​​​,则原问题化为求

∑ b i ≥ k , ∑ i = 1 n b i = D + n k D ! ∏ i = 1 n b i ! \sum_{b_i\ge k,\sum_{i=1}^n b_i=D+nk}\dfrac{D!}{\prod_{i=1}^nb_i!} bik,i=1nbi=D+nki=1nbi!D!​​​​​​​

考虑 b i ≥ k b_i\ge k bik​​​​​​ 的限制,只需将次数 < k <k​​​​​​ 的项去掉,所以

∑ b i ≥ k , ∑ i = 1 n b i = D + n k 1 ∏ i = 1 n b i ! = [ x D + n k ] ( e x − ∑ i = 0 k − 1 x i i ! ) n \sum_{b_i\ge k,\sum_{i=1}^n b_i=D+nk}\dfrac{1}{\prod_{i=1}^nb_i!}=[x^{D+nk}](e^x-\sum_{i=0}^{k-1}\dfrac{x^i}{i!})^n bik,i=1nbi=D+nki=1nbi!1=[xD+nk](exi=0k1i!xi)n
由于 ( n , k ) (n,k) (n,k)​​​​​​​ 很小,上式可以直接暴力计算,记 A ( x ) = − ∑ i = 0 k − 1 x i i ! A(x)=-\sum_{i=0}^{k-1}\dfrac{x^i}{i!} A(x)=i=0k1i!xi​​​,预处理 A ( x ) , A 2 ( x ) , . . . , A n ( x ) A(x),A^2(x),...,A^n(x) A(x),A2(x),...,An(x)​​​​

再根据 ( e x + A ( x ) ) n = ∑ i = 0 n ( n i ) A i ( x ) e ( n − i ) x (e^x+A(x))^n=\sum_{i=0}^{n}\binom{n}{i}A^i(x)e^{(n-i)x} (ex+A(x))n=i=0n(in)Ai(x)e(ni)x​​ 即可算出答案。

计算过程中出现的 1 ( D + n k − j ) ! , j ∈ [ 0 , A . l e n ] \dfrac{1}{(D+nk-j)!},j\in[0,A.len] (D+nkj)!1,j[0,A.len]​​​​ 无法快速计算,但由于整体乘上了 D ! D! D!​​​​,而 D ! ( D + n k − j ) ! \dfrac{D!}{(D+nk-j)!} (D+nkj)!D!​​​​ 可以快速计算。

最终复杂度 O ( n 2 k 2 ) O(n^2k^2) O(n2k2)

​​​​​​​​​​​​​​

#include
using namespace std;
const int N = 2510;
typedef long long ll;
const ll mod = 998244353;
ll n, k, D, tot, a[N][N], f[N];
ll fact[N], infact[N];

ll mod_pow(ll x, ll n)
{
    ll res = 1;
    while(n)
    {
        if(n & 1) res = res * x % mod;
        x = x * x % mod;
        n >>= 1;
    }
    return res;
}

void mul(ll a[], ll b[], ll c[])
{
    for(int i = 0; i <= tot; i++)
    {
        for(int j = 0; j <= tot; j++)
        {
            c[i] += a[j] * b[i - j] % mod;
            c[i] %= mod;
        }
    }
}

ll C(ll a, ll b)
{
    return fact[a] * infact[b] % mod * infact[a - b] % mod;
}

int main()
{
    scanf("%lld%lld%lld", &n, &k, &D);
    tot = n * k;

    fact[0] = infact[0] = 1;
    for(int i = 1; i <= 50; i++)
    {
        fact[i] = fact[i - 1] * i % mod;
    }

    infact[50] = mod_pow(fact[50], mod - 2);
    for(int i = 50 - 1; i >= 0; i--) infact[i] = infact[i + 1] * (i + 1) % mod;

    f[0] = 1;
    for(ll i = 1; i <= tot; i++)
    {
        ll u = i + D;
        f[i] = f[i - 1] * mod_pow(u, mod - 2) % mod;
    }

    a[0][0] = 1;
    for(int i = 0; i < k; i++) a[1][i] = mod - infact[i];
    for(int i = 2; i <= n; i++) mul(a[1], a[i - 1], a[i]);

    ll ans = 0;
    for(int i = 0; i <= n; i++)
    {
        ll res = 0;
        for(int j = 0; j <= tot; j++)
        {

            res += mod_pow(i, D + tot - j) * f[tot -j] % mod * a[n - i][j] % mod;
            res %= mod;
        }
        ans += res % mod * C(n, i) % mod;
        ans %= mod;
    }
    printf("%lld\n", ans);
    return 0;
}

9. B-Sample Game

一个东西会按某个固定的概率随机生成 [ 1 , n ] [1,n] [1,n] 内的数,生成数字 i i i 的概率是 p i p_i pi。每生成一个数之后,若它是包含之前所生成的所有数中最大的(可能是并列最大),那就再继续生成;否则停止生成,然后得分是目前为止生成序列的长度的平方。求得分的期望.

如果生成每个数字的数量都确定了,那么可以唯一确定尚未停止的合法的生成序列的前缀(比如选择的数字排好序之后就是 1 , 1 , 2 , 3 , 4 , 5 1, 1, 2, 3, 4, 5 1,1,2,3,4,5). 那么数字 i i i 对应的多项式就是
1 + p i x + ( p i x ) 2 + ( p i x ) 3 + . . . = 1 1 − p i x 1 + p_ix + (p_ix)^2 + (p_ix)^3 + ... = \dfrac{1}{1 - p_ix} 1+pix+(pix)2+(pix)3+...=1pix1
f ( x ) = ∏ i = 1 n 1 1 − p i x f(x) = \prod\limits_{i=1}^n \dfrac{1}{1-p_ix} f(x)=i=1n1pix1

x i x^i xi​ 对应的系数记为序列长度大于 i i i 的概率,即 P ( l e n > i ) P(len > i) P(len>i). 即 f ( x ) = ∑ i = 0 ∞ P ( l e n > i ) x i f(x) = \sum\limits_{i=0}^\infty P(len > i)x^i f(x)=i=0P(len>i)xi

那么期望为
∑ i = 0 ∞ ( i + 1 ) 2 ( P ( l e n > i ) − P ( l e n > i + 1 ) ) = ∑ i = 0 ∞ i 2 P ( l e n > i ) + ∑ i = 0 ∞ ( 2 i + 1 ) P ( l e n > i ) − ∑ i = 0 ∞ ( i + 1 ) 2 P ( l e n > i + 1 ) = ∑ i = 0 ∞ ( 2 i + 1 ) P ( l e n > i ) = 2 ∑ i = 0 ∞ i ∗ P ( l e n > i ) + ∑ i = 0 ∞ P ( l e n > i ) = 2 f ′ ( 1 ) + f ( 1 ) . \begin{align} &\sum\limits_{i=0}^\infty (i+1)^2\Big(P(len > i) - P(len > i + 1)\Big) \\ =& \sum\limits_{i=0}^\infty i^2P(len > i) + \sum\limits_{i=0}^\infty (2i+1) P(len > i) - \sum\limits_{i=0}^\infty (i+1)^2 P(len > i + 1) \\ =& \sum\limits_{i=0}^\infty (2i+1) P(len > i) \\ =&2 \sum\limits_{i=0}^\infty i*P(len>i) + \sum\limits_{i=0}^\infty P(len > i) \\ =& 2f'(1) + f(1). \end{align} ====i=0(i+1)2(P(len>i)P(len>i+1))i=0i2P(len>i)+i=0(2i+1)P(len>i)i=0(i+1)2P(len>i+1)i=0(2i+1)P(len>i)2i=0iP(len>i)+i=0P(len>i)2f(1)+f(1).
f ′ ( x ) = f ( x ) ∑ i = 1 n p i 1 − p i x f'(x) = f(x) \sum\limits_{i=1}^n \dfrac{p_i}{1 - p_ix} f(x)=f(x)i=1n1pixpi

代码略.

10. 城市规划

求出 n n n 个点的简单 (无重边无自环) 有标号无向连通图数目

n n n 个点的简单有标号无向连通图的数量是 f ( n ) f(n) f(n) n n n 个点的简单有标号无向图数量是 g ( n ) g(n) g(n). 则 g ( n ) = 2 C n 2 g(n) = 2^{C_n^2} g(n)=2Cn2

我们枚举 1 1 1 号点所在连通块儿的大小,可得:
g ( n ) = ∑ i = 1 n C n − 1 i − 1 f ( i ) ∗ g ( n − i ) g(n) = \sum\limits_{i=1}^nC_{n-1}^{i-1} f(i) * g(n-i) g(n)=i=1nCn1i1f(i)g(ni)
化简可得:
g ( n ) ( n − 1 ) ! = ∑ i = 1 n f ( i ) ( i − 1 ) ! g ( n − i ) ( n − i ) ! \frac{g(n)}{(n-1)!} = \sum\limits_{i=1}^n \frac{f(i)}{(i-1)!} \frac{g(n-i)}{(n-i)!} (n1)!g(n)=i=1n(i1)!f(i)(ni)!g(ni)

F ( x ) = ∑ i = 1 ∞ f ( i ) ( i − 1 ) ! x i G ( x ) = ∑ i = 0 ∞ g ( i ) i ! x i P ( x ) = ∑ i = 1 ∞ g ( i ) ( i − 1 ) ! x i F ( x ) = P ( x ) ∗ G − 1 ( x ) \begin{align} F(x) &= \sum\limits_{i=1}^\infty \frac{f(i)}{(i-1)!}x^i \\ G(x) &= \sum\limits_{i=0}^\infty \frac{g(i)}{i!}x^i \\ P(x) &= \sum\limits_{i=1}^\infty \frac{g(i)}{(i-1)!}x^i\\ F(x) &= P(x) * G^{-1}(x) \end{align} F(x)G(x)P(x)F(x)=i=1(i1)!f(i)xi=i=0i!g(i)xi=i=1(i1)!g(i)xi=P(x)G1(x)

11. 付公主的背包

这个背包最多可以装 1 0 5 10^5 105 大小的东西

付公主有 n n n 种商品,她要准备出摊了

每种商品体积为 v i v_i vi,都有无限件

给定 m m m,对于 s ∈ [ 1 , m ] s\in [1,m] s[1,m],请你回答用这些商品恰好装 s s s 体积的方案数

设方案数序列为 s s s,则其生成函数
s ( x ) = ∏ i = 1 n 1 1 − x v i s(x) = \prod\limits_{i=1}^n\dfrac{1}{1 - x^{v_i}} s(x)=i=1n1xvi1
两边取对数,对每个 ln ⁡ ( 1 − x v i ) \ln(1-x^{v_i}) ln(1xvi) x = 0 x=0 x=0 泰勒展开
ln ⁡ s ( x ) = − ∑ i = 1 n ln ⁡ ( 1 − x v i ) = ∑ i = 1 n ∑ j = 1 ∞ 1 j ( x v i ) j = ∑ i = 1 n ∑ j = 1 ∞ 1 j x j ∗ v i \begin{align} \ln s(x) &= -\sum\limits_{i=1}^n \ln(1-x^{v_i}) \\ &= \sum\limits_{i=1}^n \sum\limits_{j=1}^\infty \frac{1}{j} (x^{v_i})^j \\ &= \sum\limits_{i=1}^n \sum\limits_{j=1}^\infty \frac{1}{j} x^{j*v_i} \\ \end{align} lns(x)=i=1nln(1xvi)=i=1nj=1j1(xvi)j=i=1nj=1j1xjvi
然后两边在同时求指数函数就可以了. 答案就是
exp ⁡ { ∑ i = 1 n ∑ j = 1 ∞ 1 j x j ∗ v i } \exp\bigl\{\sum\limits_{i=1}^n \sum\limits_{j=1}^\infty \dfrac{1}{j} x^{j*v_i} \bigr\} exp{i=1nj=1j1xjvi}

12. Another thief in a Shop - HDU 6953 - Virtual Judge (vjudge.net)

生成函数

13. I love max and multiply - HDU 6971 - Virtual Judge (vjudge.net)

FWT变形

14. Forgiving Matching - HDU 6975 - Virtual Judge (vjudge.net)

FFT+字符串

你可能感兴趣的:(ACM题目整理,算法)