杭电多校第七场8月10日补题记录

C Fall with Trees

题意:给定一个完全二叉树的根节点与其左右儿子的坐标,按照每层等间距、每一层相邻儿子均等间隔排布,问这棵二叉树围成的凸包面积。

解法:记第二层的总宽度为 b 2 = x b_2=x b2=x​​​​​​​​,则第三层宽度为 b 3 = x ( 1 + 1 2 ) \displaystyle b_3=x(1+\frac{1}{2}) b3=x(1+21)​​​​​​​​,第四层宽度为 b 4 = x ( 1 + 1 2 + 1 4 ) b_4=\displaystyle x(1+\frac{1}{2}+\frac{1}{4}) b4=x(1+21+41)​​​​​​​​,依次类推有第 i i i​​​​​​​​ 层的宽度为 b i = x ( ∑ j = 0 i − 2 1 2 j ) = x ( 2 − 1 2 i − 2 ) \displaystyle b_i=x(\sum_{j=0}^{i-2}\frac{1}{2^j})=x(2-\frac{1}{2^{i-2}}) bi=x(j=0i22j1)=x(22i21)​​​​​。

记层高为 h h h​​​,则总面积为 1 2 h × [ ∑ i = 2 n ( b i + b i − 1 ) ] = h x ( 2 n − 5 + 1 2 n − 1 + 1 2 n − 2 ) \displaystyle \frac{1}{2}h \times [\sum_{i=2}^{n}(b_i+b_{i-1})]=hx(2n-5+\frac{1}{2^{n-1}}+\frac{1}{2^{n-2}}) 21h×[i=2n(bi+bi1)]=hx(2n5+2n11+2n21)​​​​,带入公式即可。

D Link with Balls

题意:有 2 n 2n 2n 个箱子,第 2 i − 1 2i-1 2i1 个箱子可以拿 k i ki ki 个球, k ∈ Z k \in \Z kZ,第 2 i − 1 2i-1 2i1 个箱子至多拿 i i i 个球。问拿出 m m m 个球的方案数。

解法:考虑生成函数,记 f ( x ) f(x) f(x)​​​ 为方案数的生成函数,对于第 2 i − 1 2i-1 2i1​​​ 个箱子,其生成函数 g i ( x ) = ∑ j = 1 ∞ x i j = 1 1 − x i \displaystyle g_i(x)=\sum_{j=1}^{\infty} x^{ij}=\frac{1}{1-x^i} gi(x)=j=1xij=1xi1​​​,对于第 2 i 2i 2i​​​ 个箱子,其生成函数 h i ( x ) = ∑ j = 0 i x j = 1 − x i + 1 1 − x \displaystyle h_i(x)=\sum_{j=0}^{i}x^j=\frac{1-x^{i+1}}{1-x} hi(x)=j=0ixj=1x1xi+1​​​。 f ( x ) = ∏ i = 1 n g i ( x ) ∏ i = 1 n h i ( x ) = 1 1 − x 1 − x 2 1 − x 1 1 − x 2 1 − x 3 1 − x ⋯ 1 1 − x n 1 − x n + 1 1 − x = 1 − x n + 1 ( 1 − x ) n + 1 \displaystyle f(x)=\prod_{i=1}^ng_i(x)\prod_{i=1}^nh_i(x)=\frac{1}{1-x}\frac{{\cancel{1-x^2}}}{1-x} \frac{1}{\cancel{1-x^2}}\frac{\cancel{1-x^3}}{1-x} \cdots \frac{1}{\cancel{1-x^n}}\frac{1-x^{n+1}}{1-x}=\frac{1-x^{n+1}}{(1-x)^{n+1}} f(x)=i=1ngi(x)i=1nhi(x)=1x11x1x2 1x2 11x1x3 1xn 11x1xn+1=(1x)n+11xn+1​​​。

考虑 1 ( 1 − x ) n + 1 \displaystyle \frac{1}{(1-x)^{n+1}} (1x)n+11​​​​ 的无穷级数展开。首先 f ( x ) = 1 1 − x = 1 + x + x 2 + ⋯ + x n + ⋯ \displaystyle f(x)=\frac{1}{1-x}=1+x+x^2+\cdots+x^n+\cdots f(x)=1x1=1+x+x2++xn+​​​​,对等式两侧求 n n n​​​​ 阶导数有 f ( n ) ( x ) = n ! 1 ( 1 − x ) n + 1 = n ! + ( n + 1 ) ! 1 ! x + ( n + 2 ) ! 2 ! x 2 + ⋯ + ( n + m ) ! m ! x m + ⋯ \displaystyle f^{(n)}(x)=n!\frac{1}{(1-x)^{n+1}}=n!+\frac{(n+1)!}{1!}x+\frac{(n+2)!}{2!}x^2+\cdots+\frac{(n+m)!}{m!}x^m+\cdots f(n)(x)=n!(1x)n+11=n!+1!(n+1)!x+2!(n+2)!x2++m!(n+m)!xm+​。因而 x m x^m xm​ 项的系数等于 ( n + m ) ! n ! m ! = ( n + m n ) \displaystyle \frac{(n+m)!}{n!m!}=\dbinom{n+m}{n} n!m!(n+m)!=(nn+m)。答案等于 x m x^m xm 项系数减去 x m − n − 1 x^{m-n-1} xmn1 项系数,即 ( n + m n ) − ( m − 1 n ) \dbinom{n+m}{n}-\dbinom{m-1}{n} (nn+m)(nm1)

E Link with EQ

题意:有一张长度为 n n n 的桌子,第一个人随机选取座位,从第二个人开始选取距离已经就坐的所有人最远的一个位置就坐。当最远距离仅为 1 1 1 时停止就坐,统计就坐人数。问就坐人数的期望。

解法:首先考虑一个 DP—— f i f_i fi 表示长度为 i i i 的桌子,两侧都坐着人时最多能坐多少人。那么这个递推非常容易—— f i = f ⌊ i 2 ⌋ + f i − 1 − ⌊ i 2 ⌋ + 1 f_i=f_{\left \lfloor \frac{i}{2}\right \rfloor}+ f_{i-1-\left \lfloor \frac{i}{2}\right \rfloor}+1 fi=f2i+fi12i+1。其含义为,新来的一个人只能就坐于最中间的位置,即 ⌊ i 2 ⌋ \displaystyle \left\lfloor \frac{i}{2} \right \rfloor 2i 的位置。那么剩下的位置被切分成为了两个新的两侧都坐有人的情况—— [ 1 , ⌊ i 2 ⌋ ] \displaystyle \left[1,\left\lfloor \frac{i}{2} \right \rfloor \right] [1,2i] [ ⌊ i 2 ⌋ + 1 , i ] \displaystyle \left[\left\lfloor \frac{i}{2} \right\rfloor+1,i\right] [2i+1,i]。因而可以递推。

考虑 g i g_i gi 表示只有单侧坐着人的情况——那么 g i = f i − 1 + 1 , i ≥ 2 g_i=f_{i-1}+1,i\geq 2 gi=fi1+1,i2。特别的, g 1 = 0 g_1=0 g1=0​。如果单侧坐着人,那么下一个来的人就只能坐在另一头,然后情况回归到上面的情况。

那么答案呼之欲出了——枚举第一个人所处的位置, h n = 1 n ∑ i = 1 n g ( i − 1 ) + g ( n − i ) + 1 = 1 + 2 n ∑ i = 1 n g ( i − 1 ) \displaystyle h_n=\frac{1}{n}\sum_{i=1}^n g(i-1)+g(n-i)+1=1+\frac{2}{n}\sum_{i=1}^ng(i-1) hn=n1i=1ng(i1)+g(ni)+1=1+n2i=1ng(i1)​。使用前缀和优化即可做到 O ( 1 ) O(1) O(1) 查询。

#include <cstdio>
#include <algorithm>
using namespace std;
const long long mod = 1000000007ll;
const int N = 1000000;
long long power(long long a,long long x)
{
    long long ans = 1;
    while(x)
    {
        if(x&1)
            ans = ans * a % mod;
        a = a * a % mod;
        x >>= 1;
    }
    return ans;
}
long long inv(long long x)
{
    return power(x, mod - 2);
}
long long f[N + 5], g[N + 5], sumg[N + 5];
int main()
{
    int t, n;
    f[1] = f[2] = 0;
    for (int i = 3; i <= N;i++)
    {
        int half = (i - 1) / 2;
        f[i] = f[half] + f[i - 1 - half] + 1;
    }
    for (int i = 2; i <= N;i++)
        g[i] = f[i - 1] + 1;
    for (int i = 1; i <= N;i++)
        sumg[i] = (sumg[i - 1] + g[i]) % mod;
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d", &n);
        printf("%lld\n", (1 + 2ll * inv(n) * sumg[n - 1] % mod) % mod);
    }
    return 0;
}

F Link with Grenade

题意:有一个人向任意方向等概率抛掷一枚炸弹, t t t 时刻后爆炸,若 t t t 时刻炸弹离他不足 r r r 的距离则会被炸死,问被炸死的概率。

解法:任意方向等概率即以投掷点为中心做一球面,球面上各点等概率。因而不是水平投掷角等概率——这其实是不等概率的。真正的等概率应该放在球面上处理。

记投掷方向与 z z z​​​ 轴正向成 θ \theta θ​​​ 角。则由抛体运动公式, x = v t sin ⁡ θ , y = v cos ⁡ θ t − 1 2 g t 2 \displaystyle x=vt\sin \theta,y=v\cos \theta t-\frac{1}{2}gt^2 x=vtsinθ,y=vcosθt21gt2​​​。 r ≥ x 2 + y 2 = v 2 t 2 + g 2 t 4 4 − v g t 3 cos ⁡ θ \displaystyle r\geq \sqrt{x^2+y^2}=\sqrt{v^2t^2+\frac{g^2t^4}{4}-vgt^3\cos \theta} rx2+y2 =v2t2+4g2t4vgt3cosθ ​​​。则可推出 cos ⁡ θ ≤ 4 r 2 − 4 v 2 t 2 − g 2 t 4 4 v g t 3 \displaystyle \cos \theta\leq \frac{4r^2-4v^2t^2-g^2t^4}{4vgt^3} cosθ4vgt34r24v2t2g2t4​​​​​​ 为一有理数。

首先判断一定炸死或者一定不炸死的情况——当 4 r 2 − 4 v 2 t 2 − g 2 t 4 ≥ 4 v g t 3 4r^2-4v^2t^2-g^2t^4\geq 4vgt^3 4r24v2t2g2t44vgt3 时一定炸不死,当 4 r 2 − 4 v 2 t 2 − g 2 t 4 ≤ − 4 v g t 3 4r^2-4v^2t^2-g^2t^4\leq -4vgt^3 4r24v2t2g2t44vgt3 时一定炸死。考察球冠的表面积—— S = π r 2 ( 1 − cos ⁡ θ ) S=\pi r^2(1-\cos \theta) S=πr2(1cosθ) 可知 cos ⁡ θ \cos \theta cosθ 为等概率(注意 θ \theta θ 不是等概率!),概率 p = S 4 π r 2 = 1 − cos ⁡ θ 2 \displaystyle p=\frac{S}{4\pi r^2}=\frac{1-\cos \theta}{2} p=4πr2S=21cosθ。带入上面算好的 cos ⁡ θ \cos \theta cosθ 即可。

#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
const double pi = acos(-1);
const int g = 10;
const long long mod = 1000000007ll;
long long power(long long a,long long x)
{
    long long ans = 1;
    while(x)
    {
        if(x&1)
            ans = ans * a % mod;
        a = a * a % mod;
        x >>= 1;
    }
    return ans;
}
long long inv(long long x)
{
    return power(x, mod - 2);
}
int main()
{
    long long T, t, v, r;
    scanf("%lld", &T);
    while(T--)
    {
        scanf("%lld%lld%lld", &t, &v, &r);
        long long up = 25 * t * t * t * t - r * r + v * v * t * t;
        long long down = g * t * t * t * v;
        if(up<=-down || up>=down)
        {
            if(up<0)
                printf("0\n");
            else
                printf("1\n");
            continue;
        }
        printf("%lld\n", ((1 + (up % mod * inv(down) % mod) % mod) % mod * inv(2) % mod + mod) % mod);
    }
    return 0;
}

G Link with Limit

题意:给定一个定义域与值域均在 { 1 , 2 , … , n } \{ 1,2,\dots,n\} {1,2,,n}​ 上的函数 f ( x ) f(x) f(x)​,记 f 1 ( x ) = f ( x ) f_1(x)=f(x) f1(x)=f(x)​, f i ( x ) = f i − 1 ( f ( x ) ) , i ≥ 2 f_{i}(x)=f_{i-1}(f(x)),i\geq 2 fi(x)=fi1(f(x)),i2​,判断 ∀ i ∈ [ 1 , n ] \forall i \in [1,n] i[1,n]​, lim ⁡ j → ∞ 1 j ∑ k = 1 j f k ( i ) \displaystyle \lim_{j \to \infty} \frac{1}{j}\sum_{k=1}^{j}f_k(i) jlimj1k=1jfk(i)​ 是否相等。

解法:容易发现只要存在环,则环上的点与能到达这个环的点的所求值均为环上所有点的均值。因而拓扑排序找到所有的环,求出每一个环上值的均值,判断是否相等即可。

#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 100000;
int a[N + 5], in[N + 5];
int main()
{
    int t, n;
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d", &n);
        for (int i = 1; i <= n;i++)
            in[i] = 0;
        for (int i = 1; i <= n;i++)
        {
            scanf("%d", &a[i]);
            in[a[i]]++;
        }
        queue<int> q;
        for (int i = 1; i <= n;i++)
            if(!in[i])
                q.push(i);
        while(!q.empty())
        {
            int tp = q.front();
            q.pop();
            in[a[tp]]--;
            if(!in[a[tp]])
                q.push(a[tp]);
        }
        long long siz = 0, sum = 0;
        bool flag = 1;
        for (int i = 1; i <= n;i++)
        {
            if(!in[i])
                continue;
            long long temp_siz = 0, temp_sum = 0;
            while(in[i])
            {
                temp_siz++;
                temp_sum += i;
                in[i] = 0;
                i = a[i];
            }
            if(!sum)
            {
                sum = temp_sum;
                siz = temp_siz;
            }
            else
                if(sum*temp_siz!=temp_sum*siz)
                {
                    flag = 0;
                    break;
                }
        }
        if(flag)
            printf("YES\n");
        else
            printf("NO\n");
    }
    return 0;
}

J Yiwen with Formula

题意:给定一个长度为 n n n 的序列 { a n } \{ a_n\} {an},找到其全部的子序列(允许不连续)的和的乘积。即, ∏ k = 1 n ∑ i = 1 k a b k \displaystyle \prod_{k=1}^{n} \sum_{i=1}^{k}a_{b_k} k=1ni=1kabk,满足 1 ≤ b 1 < b 2 < ⋯ < b k ≤ n 1\leq b_1<b_2< \cdots<b_k \leq n 1b1<b2<<bkn​。 n ≤ 1 × 1 0 5 n \leq 1\times 10^5 n1×105 0 ≤ a i ≤ 1 × 1 0 5 0 \leq a_i \leq 1\times 10^5 0ai1×105 ∑ a i ≤ 1 × 1 0 5 \sum a_i \leq 1\times 10^5 ai1×105

解法:考虑到 ∑ a i ≤ 1 × 1 0 5 \sum a_i \leq 1\times 10^5 ai1×105,因而不妨假设和为 x x x 出现的次数为 f x f_x fx,则最终答案为 ∏ i = 1 ∑ a i x f x \displaystyle \prod_{i=1}^{\sum a_i} x^{f_x} i=1aixfx。若 a i = 0 a_i=0 ai=0 则答案直接为 0 0 0,因而下问不再赘述这种特殊情况。

考虑 f x f_x fx​​ 如何快速求得。显然一个非常容易想到的方法就是 O ( n 2 ) O(n^2) O(n2)​​ 的 01 背包。我们尝试模拟一下这一过程。对于第 i i i​​ 个数,可以选择也可以不选择, f x ← f x + f x − a i f_x \leftarrow f_x+f_{x-a_i} fxfx+fxai​。​从整个数组来看,其本质为整个数组向右平移 a i a_i ai 位后再加回到原数组。使用生成函数来观察这一过程—— g ( x ) = ∑ i = 0 ∑ a i f i x i \displaystyle g(x)=\sum_{i=0}^{\sum a_i} f_i x^i g(x)=i=0aifixi,则经过第 i i i 个数之后, g ( x ) ← g ( x ) + x a i g ( x ) g(x) \leftarrow g(x)+x^{a_i}g(x) g(x)g(x)+xaig(x)。记初始的 g ( x ) = 1 g(x)=1 g(x)=1,则总的生成函数可以记为 ∏ i = 1 n ( 1 + x a i ) \displaystyle \prod_{i=1}^n(1+x^{a_i}) i=1n(1+xai)。我们只需要找到它的每一个系数即可。

注意到我们求出来的系数都是作为指数出现的,因而它们应当对 φ ( p ) = p − 1 \varphi(p)=p-1 φ(p)=p1 取模。这一过程可以套用 MTT (任意模数多项式乘法)的板子在 O ( n log ⁡ n ) O(n \log n) O(nlogn) 时间内实现。最后逐项乘起来即可。

#include <bits/stdc++.h>
#define fp(i,a,b) for(int i=(a),_##i=(b)+1;i<_##i;++i)
#define fd(i,a,b) for(int i=(a),_##i=(b)-1;i>_##i;--i)
#define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
using namespace std;
const int N = 2e5 + 5, P = 998244353;
using db = double;
using arr = int[N];
using ll = long long;
/*---------------------------------------------------------------------------*/
vector<int>inv;
int getInv(int L){
    if(inv.empty())inv={0,1};
    fp(i,inv.size(),L)inv.push_back((ll)(P-P/i)*inv[P%i]%P);
    return inv[L];
}
int POW(ll a,int b,ll x=1){for(;b>=1;b>>=1,a=a*a%P)if(b&1)x=x*a%P;return x;}
int INV(int a){return a<N?getInv(a):POW(a,P-2);}
#define ADD(a, b) ((a) += (b), (a) >= P ? (a) -=P : 0) // (a += b) %= P
#define SUB(a, b) ((a) -= (b), (a) < 0 ? (a) += P: 0)  // ((a -= b) += P) %= P
#define ADDE(a, b, c) ((a) = (b), ADD(a, c))           // a = (b + c) % P
#define SUBE(a, b, c) ((a) = (b), SUB(a, c))           // a = (b - c + P) % P
namespace MTT {
    long double const pi = acos(-1);
    struct comp {
        long double r, i;
        comp() { r = i = 0; }
        comp(long double x, long double y) { r = x, i = y; }
        comp conj() { return comp(r, -i); }
        friend comp operator+(comp x, comp y) { return comp(x.r + y.r, x.i + y.i); }
        friend comp operator-(comp x, comp y) { return comp(x.r - y.r, x.i - y.i); }
        friend comp operator*(comp x, comp y) { return comp(x.r * y.r - x.i * y.i, x.i * y.r + x.r * y.i); }
    };
    typedef long long ll;
    int r[400005];
    comp a[400005], b[400005], c[400005], d[400005];
    void fft(comp *f, int n, int op) {
        for (int i = 1; i < n; i++)r[i] = (r[i >> 1] >> 1) + ((i & 1) ? (n >> 1) : 0);
        for (int i = 1; i < n; i++)if (i < r[i])swap(f[i], f[r[i]]);
        for (int len = 2; len <= n; len <<= 1) {
            int q = len >> 1;
            comp wn = comp(cos(pi / q), op * sin(pi / q));
            for (int i = 0; i < n; i += len) {
                comp w = comp(1, 0);
                for (int j = i; j < i + q; j++, w = w * wn) {
                    comp d = f[j + q] * w;
                    f[j + q] = f[j] - d;
                    f[j] = f[j] + d;
                }
            }
        }
    }
    void mtt(vector<int>&f, vector<int>&g, vector<int>&h, int n, int p) {
        for (int i = 0; i < n; i++)
            a[i].r = (f[i] >> 15), a[i].i = (f[i] & 32767),
            c[i].r = (g[i] >> 15), c[i].i = (g[i] & 32767);
        fft(a, n, 1), fft(c, n, 1);
        for (int i = 1; i < n; i++)b[i] = a[n - i].conj();
        b[0] = a[0].conj();
        for (int i = 1; i < n; i++)d[i] = c[n - i].conj();
        d[0] = c[0].conj();
        for (int i = 0; i < n; i++) {
            comp
                    aa = (a[i] + b[i]) * comp(0.5, 0),
                    bb = (a[i] - b[i]) * comp(0, -0.5),
                    cc = (c[i] + d[i]) * comp(0.5, 0),
                    dd = (c[i] - d[i]) * comp(0, -0.5);
            a[i] = aa * cc + comp(0, 1) * (aa * dd + bb * cc), b[i] = bb * dd;
        }
        fft(a, n, -1), fft(b, n, -1);
        for (int i = 0; i < n; i++) {
            int
                    aa = (ll) (a[i].r / n + 0.5) % p,
                    bb = (ll) (a[i].i / n + 0.5) % p,
                    cc = (ll) (b[i].r / n + 0.5) % p;
            h[i] = ((1ll * aa * (1 << 30) + 1ll * bb * (1 << 15) + cc) % p + p) % p;
        }
    }
}
class Poly{
    #define FF0 fp(i,0,L-1)
    #define FF1 fp(i,0,F.L-1)
    vector<int> a;
public:
    Poly(const vector<int>&t){a=t;}
    Poly(int n=0){a.assign(n+1,0);}
    int deg(){return(int)a.size()-1;}
    int&operator[](const int&x){return a[x];}
    void resize(size_t n){a.resize(n);}
    friend Poly operator*(Poly A,Poly B){
        int L=A.deg()+B.deg()+1,n=1;
        while(n<=L)n<<=1;
        Poly C(n);A.resize(n),B.resize(n);
        MTT::mtt(A.a,B.a,C.a,n,P-1);
        return C.resize(L),C;
    }
    #undef FF0
    #undef FF1
};
int n,a[N];
Poly divide(int L,int R){
    if(L==R){
        Poly k(a[L]);
        k[0]=k[a[L]]=1;
        return k;
    }
    int m=(L+R)>>1;
    return divide(L,m)*divide(m+1,R);
}
void Solve(){
    int ans=1;
    fp(i,1,n)scanf("%d",a+i),ans*=(a[i]!=0);
    Poly f=divide(1,n);
    fp(i,1,f.deg())ans=POW(i,f[i],ans);
    printf("%d\n",ans);
}
int main(){
#ifdef LOCAL
    file("s");
#endif
    scanf("%*d");
    while(~scanf("%d",&n))
        Solve();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值