2021牛客暑期多校训练营9

2021牛客暑期多校训练营9

C、Cells

LGV引理代进去算算,会发现是个范德蒙德行列式。

于是问题转化为快速求出一个集合中任意两个元素差的乘积。然后赛场上卡了。

考虑到元素范围在 1 0 6 10^6 106,发现元素差的取值范围不大,于是可以计算每个差对答案的贡献,即每个差会被乘几次。于是可以用生成函数的考虑方法,把元素放在指数上,前面的系数表示个数。那么两个生成函数一乘就能算出来取 α i + α j \alpha_i+\alpha_j αi+αj​​的取法种数。而这题要求差,所以把 α j \alpha_j αj换成 − α j -\alpha_j αj即可。由于ntt没办法算负指数,可以统一加一个偏移量计算。

最后统计答案即可。

#include <bits/stdc++.h>
typedef long long ll;
const int MAXN = 5e6 + 10, MOD = 998244353;
int w[MAXN];
ll inv[MAXN], fac[MAXN];
ll qpow(ll a, ll b)
{
    ll res = 1; a %= MOD;
    while (b)
    {
        if (b & 1) res = res * a % MOD;
        a = a * a % MOD; b >>= 1;
    }
    return res;
}
ll a[MAXN], b[MAXN], rev[MAXN], k;
const int UP = 1000001;
int qpow(int a, int b)
{
    int res = 1; a %= MOD;
    while (b)
    {
        if (b & 1) res = 1ll * res * a % MOD;
        a = 1ll * a * a % MOD; b >>= 1;
    }
    return res;
}
void ntt(ll *a, int n, int opt)
{
    for (int i = 0; i < n; ++i)
        if (i < rev[i]) std::swap(a[i], a[rev[i]]);
    for (int h = 2; h <= n; h <<= 1)
    {
        int gn = qpow(3, (MOD - 1) / h);
        if (opt < 0) gn = qpow(gn, MOD - 2);
        for (int j = 0, g = 1; j < n; j += h, g = 1)
            for (int k = j; k < j + h / 2; ++k, g = 1ll * g * gn % MOD)
            {
                int u = a[k], v = 1ll * g * a[k + h / 2] % MOD;
                a[k] = (u + v) % MOD; a[k + h / 2] = (u - v) % MOD;
            }
    }
    if (opt < 0)
    {
        ll inv = qpow(n, MOD - 2);
        for (int i = 0; i < n; ++i)
            a[i] = 1ll * a[i] * inv % MOD;
    }
}
int main()
{
    int n; scanf("%d", &n);
    for (int i = 1; i <= n; ++i)    
        scanf("%d", &w[i]), w[i]++;
    fac[0] = inv[0] = 1;
    for (int i = 1; i <= n; ++i)
    {
        fac[i] = fac[i - 1] * i % MOD;
        inv[i] = inv[i - 1] * qpow(i, MOD - 2) % MOD;
    }
    ll res = 1;
    for (int i = 1; i <= n; ++i)
        res = res * inv[i] % MOD;
    for (int i = 1; i <= n; ++i)
        res = res * w[i] % MOD;
    for (int i = 1; i <= n; ++i)
        a[w[i]]++, b[UP - w[i]]++;
    int tot = 1, k = 0;
    while (tot <= 2 * UP + 1)
        ++k, tot <<= 1;
    for (int i = 1; i <= tot; ++i)
        rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << k - 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);
    for (int i = 0; i <= tot; ++i)
        a[i] = (a[i] + MOD) % MOD;
    for (int i = UP + 1; i <= 2 * UP; ++i)
        res = res * qpow(ll(i - UP), a[i]) % MOD;
    printf("%lld\n", res);
    return 0;
}

E、Eyjafjalla

倍增找到祖先中深度最小的小于等于 r r r的点,然后询问该子树中大于等于 l l l的点有多少个即可。

主席树瞎写写就过了。

#include <bits/stdc++.h>
typedef long long ll;
const int MAXN = 3e5 + 10, INF = 0x3f3f3f3f;
std::vector < int > G[MAXN], alls;
int n, m, t[MAXN], fa[MAXN][32], dist[MAXN], vis[MAXN], sz[MAXN];
int que[MAXN], l, r, root[MAXN], id[MAXN];
int cnt;
struct node
{
    int lson, rson, val;
}tr[MAXN * 40];
struct Query
{
    int root, l, r;
}Q[MAXN];
int idx = 0;
int getidx(int x)
{
    return std::lower_bound(alls.begin(), alls.end(), x) - alls.begin() + 1;
}
int build(int l, int r)
{
    if (l == r) return ++idx;
    int p = ++idx, mid = l + r >> 1;
    tr[p].lson = build(l, mid); tr[p].rson = build(mid + 1, r);
    return p;
}
int insert(int now, int prv, int l, int r)
{
    int pidx = ++idx;
    tr[pidx].val = tr[prv].val + 1;
    if (l == r) return pidx;
    int mid = l + r >> 1;
    if (now <= mid) tr[pidx].lson = insert(now, tr[prv].lson, l, mid), tr[pidx].rson = tr[prv].rson;
    else tr[pidx].lson = tr[prv].lson, tr[pidx].rson = insert(now, tr[prv].rson, mid + 1, r);
    return pidx;
}
int query(int p, int q, int t, int s, int l, int r)
{
    if (l <= t && s <= r) return tr[q].val - tr[p].val;
    int mid = t + s >> 1, ans = 0;
    if (l <= mid) ans += query(tr[p].lson, tr[q].lson, t, mid, l, r);
    if (r > mid) ans += query(tr[p].rson, tr[q].rson, mid + 1, s, l, r);
    return ans;
}
void dfs(int now, int f)
{
    sz[now] = 1; ++cnt;
    root[cnt] = insert(getidx(t[now]), root[cnt - 1], 1, m);
    id[now] = cnt;
    for (auto &i : G[now])
    {
        if (i == f) continue;
        dfs(i, now);
        sz[now] += sz[i];
    }
}
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n - 1; ++i)
    {
        int u, v; scanf("%d%d", &u, &v);
        G[u].push_back(v); G[v].push_back(u);
    }
    t[0] = INF;
    for (int i = 1; i <= n; ++i)
        scanf("%d", &t[i]), alls.push_back(t[i]);
    int q; scanf("%d", &q);
    for (int i = 1; i <= q; ++i)
    {
        scanf("%d%d%d", &Q[i].root, &Q[i].l, &Q[i].r);
        alls.push_back(Q[i].l); alls.push_back(Q[i].r);
    }
    std::sort(alls.begin(), alls.end());
    alls.erase(std::unique(alls.begin(), alls.end()), alls.end());
    m = alls.size();
    dist[1] = 1, vis[1] = 1;
    que[1] = 1, l = r = 1;
    while (l <= r)
    {
        int u = que[l++];
        for (auto &i : G[u])
            if (!vis[i])
            {
                dist[i] = dist[u] + 1, fa[i][0] = u;
                que[++r] = i, vis[i] = 1;
                for (int j = 1; j <= 31; ++j)
                    fa[i][j] = fa[fa[i][j - 1]][j - 1];
            }
    }
    root[0] = build(1, m); dfs(1, -1);
    for (int i = 1; i <= q; ++i)
    {
        int rx = Q[i].root;
        if (t[rx] > Q[i].r || t[rx] < Q[i].l)
        {
            printf("0\n");
            continue;
        }
        for (int j = 31; j >= 0; --j)
            if (t[fa[rx][j]] <= Q[i].r) rx = fa[rx][j];
        printf("%d\n", query(root[id[rx] - 1], root[id[rx] + sz[rx] - 1], 1, m, getidx(Q[i].l), getidx(Q[i].r)));
    }
    return 0;
}

H、Happy Number

看起来就和三进制有关系,但这个三进制的数值表示是 1 1 1 3 3 3。把 n n n转换为三进制之后对于每个小于等于 0 0 0的位向高位借一个 3 3 3即可。

#include <bits/stdc++.h>
typedef long long ll;
int main()
{
    int n; scanf("%d", &n);
    std::vector < int > a;
    while (n)
        a.push_back(n % 3), n /= 3;
    for (int i = 0; i < (int)a.size() - 1; ++i)
        if (a[i] <= 0) a[i] += 3, a[i + 1]--;
    while (!a.back()) a.pop_back();
    std::reverse(a.begin(), a.end());
    for (auto &i : a)
        if (i == 1) printf("2");
        else if (i == 2) printf("3");
        else printf("6");
    printf("\n");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值