【多校训练】2023杭电多校4补题

官方数据和标程:2023杭电多校资料包

难度评价

7题慢一百名,5题快两百名,4题中四百名。
C、F、J、L签到,D、G、K中等,B、E、H、I困难,A大难。


A. Number Table

数学DP

过难


B. Simple Tree Problem

数据结构

要求维护子树內点权计数信息,支持查询指定范围內最大值,还要同步维护整棵树抠掉当前子树后的信息,常规想法是线段树合并。
维护子树信息的方法是显然的,但维护整棵树抠掉当前子树后的信息却比较麻烦,这是因为即使预处理出一棵完整的权值线段树,在DFS过程中,抠除的结点信息也难以恢复。
注意到这正是树上启发式合并的优势之一,即访问重儿子前,所有访问的轻儿子的影响都不会保留,于是我们可以考虑一种 O ( N l o g 2 N ) O(Nlog^2N) O(Nlog2N) 的方法——直接做树上启发式合并,用两棵动态开点权值线段树维护信息,一棵加点(初始没有任何信息),一棵删点(初始有完整信息)。

数据范围很大, N ≤ 1 0 6 N \leq 10^6 N106,并且不离散化的话值域达到 1 0 9 10^9 109,内存和时间上都过于极限,为了卡过同时使用了离散化、前向星、快读。

#include <bits/stdc++.h>
using namespace std;

namespace fastIO
{
#ifndef LOCAL
#define getchar() p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin), p1 == p2) ? EOF : *p1++
#define putchar(x) (p3 - obuf < 1000000) ? (*p3++ = x) : (fwrite(obuf, p3 - obuf, 1, stdout), p3 = obuf, *p3++ = x)
    char buf[1000000], *p1 = buf, *p2 = buf, obuf[1000000], *p3 = obuf;
    inline void flush() { fwrite(obuf, p3 - obuf, 1, stdout); }
#endif
    inline int read()
    {
        int s = 0;
        char ch = getchar();
        while (ch < '0' || ch > '9')
            ch = getchar();
        while (ch >= '0' && ch <= '9')
        {
            s = s * 10 + ch - '0';
            ch = getchar();
        }
        return s;
    }
    inline void write(int x)
    {
        if (x > 9)
            write(x / 10);
        putchar(x % 10 + '0');
    }
};
using namespace fastIO;

#define ls tr[rt].lc
#define rs tr[rt].rc
constexpr int MAXN = 1e6 + 5, N = 2e6;
struct SegmentTree
{
    struct Node
    {
        int lc, rc, mx;
    } tr[MAXN * 60];
    int tot, root;

    void init() { tr[1] = Node(), root = tot = 1; }

    void pushUp(int rt) { tr[rt].mx = max(tr[ls].mx, tr[rs].mx); }

    void add(int val, int C, int l, int r, int &rt)
    {
        if (!rt)
            rt = ++tot, tr[rt] = Node();
        if (l == r)
        {
            tr[rt].mx += C;
            return;
        }
        int mid = (l + r) >> 1;

        if (val <= mid)
            add(val, C, l, mid, ls);
        else
            add(val, C, mid + 1, r, rs);
        pushUp(rt);
    }

    int query(int L, int R, int y, int l, int r, int rt)
    {
        int mid = (l + r) >> 1;
        if (L <= l && r <= R)
        {
            if (tr[rt].mx < y)
                return 0;
            else if (l == r)
                return l;
            else if (tr[rs].mx >= y)
                return query(L, R, y, mid + 1, r, rs);
            else
                return query(L, R, y, l, mid, ls);
        }

        if (L > mid)
            return query(L, R, y, mid + 1, r, rs);
        else if (R <= mid)
            return query(L, R, y, l, mid, ls);
        else
            return max(query(L, R, y, l, mid, ls), query(L, R, y, mid + 1, r, rs));
    }
} t1, t2;
int L[MAXN], R[MAXN], id[MAXN], sz[MAXN], son[MAXN], dfn;
int a[MAXN], ans[MAXN], lsh[MAXN * 2];
int head[MAXN], tot = 1;
unordered_map<int, int> mp;
struct Edge
{
    int to, next, w;
} e[MAXN * 2];
void add(int u, int v, int w)
{
    e[++tot].to = v, e[tot].w = w, e[tot].next = head[u];
    head[u] = tot;
}

void dfs1(int v, int fa)
{
    L[v] = ++dfn;
    id[dfn] = v;
    sz[v] = 1;
    son[v] = 0;
    for (int i = head[v]; i; i = e[i].next)
    {
        int u = e[i].to;
        if (u == fa)
            continue;
        dfs1(u, v);
        sz[v] += sz[u];
        if (sz[u] > sz[son[v]])
            son[v] = u;
    }
    R[v] = dfn;
}

void dfs2(int v, int fa, int k, int eid, bool keep)
{
    int soneid = 0, sonw = 0;
    for (int i = head[v]; i; i = e[i].next)
    {
        int u = e[i].to, w = e[i].w;
        if (u == son[v])
            soneid = i, sonw = w;
        else if (u != fa)
            dfs2(u, v, w, i, 0);
    }
    if (son[v])
        dfs2(son[v], v, sonw, soneid, 1);

    t1.add(a[v], 1, 1, N, t1.root);
    t2.add(a[v], -1, 1, N, t2.root);
    for (int j = head[v]; j; j = e[j].next)
    {
        int u = e[j].to;
        if (u == son[v] || u == fa)
            continue;
        for (int i = L[u]; i <= R[u]; i++)
        {
            int x = a[id[i]];
            t1.add(x, 1, 1, N, t1.root);
            t2.add(x, -1, 1, N, t2.root);
        }
    }
    ans[eid / 2] = max(t1.query(1, N, mp[k], 1, N, 1), t2.query(1, N, mp[k], 1, N, 1));

    if (!keep)
    {
        for (int i = L[v]; i <= R[v]; i++)
            t2.add(a[id[i]], 1, 1, N, t2.root);
        t1.init();
    }
}

int main()
{
    int size(512 << 20);
    __asm__("movq %0, %%rsp\n" ::"r"((char *)malloc(size) + size));
    int t = read();
    while (t--)
    {
        int n = read(), num = 0;
        for (int i = 1; i <= n; i++)
            a[i] = read(), lsh[++num] = a[i];
        for (int i = 1; i < n; i++)
        {
            int v = read(), u = read(), w = read();
            add(v, u, w), add(u, v, w);
            lsh[++num] = w;
        }
        sort(lsh + 1, lsh + num + 1);
        num = unique(lsh + 1, lsh + num + 1) - lsh - 1;
        for (int i = 1; i <= num; i++)
            mp[lsh[i]] = i;
        for (int i = 1; i <= n; i++)
            a[i] = mp[a[i]];

        t1.init(), t2.init();
        for (int i = 1; i <= n; i++)
            t2.add(a[i], 1, 1, N, t2.root);
        dfs1(1, 0);
        dfs2(1, 0, 1e9, 0, 1);
        for (int i = 1; i < n; i++)
            write(lsh[ans[i]]), putchar('\n');

        fill(head + 1, head + n + 1, 0);
        dfn = 0, tot = 1;
        mp.clear();
    }
    flush();

    exit(0);
}

C. Simple Set Problem

数据结构贪心

十分类似上一场的题目Operation Hope。目标仍然是尽可能减小极差,如果只有增大操作,我们就能便捷地维护,这实际上容易做到。
将所有集合从小到大排序,并分别将第一个数加入到堆中,此后每次取出堆中第一个最小的数,将其增大,即换入对应集合的下一个数。如果已经达到该集合末尾,则极差无法减小,操作结束。

注:略卡常,加快读并使用数组可轻松通过。

#include <bits/stdc++.h>
using namespace std;

namespace fastIO
{
#ifndef LOCAL
#define getchar() p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin), p1 == p2) ? EOF : *p1++
#define putchar(x) (p3 - obuf < 1000000) ? (*p3++ = x) : (fwrite(obuf, p3 - obuf, 1, stdout), p3 = obuf, *p3++ = x)
    char buf[1000000], *p1 = buf, *p2 = buf, obuf[1000000], *p3 = obuf;
    inline void flush() { fwrite(obuf, p3 - obuf, 1, stdout); }
#endif
    inline int read()
    {
        int s = 0, f = 1;
        char ch = getchar();
        while (ch < '0' || ch > '9')
        {
            if (ch == '-')
                f = -1;
            ch = getchar();
        }
        while (ch >= '0' && ch <= '9')
        {
            s = s * 10 + ch - '0';
            ch = getchar();
        }
        return s * f;
    }
    inline void write(long long x)
    {
        if (x < 0)
        {
            putchar('-');
            x = -x;
        }
        if (x > 9)
            write(x / 10);
        putchar(x % 10 + '0');
    }
};
using namespace fastIO;

using tpl = tuple<int, int, int>;
vector<int> vec[1000005];

int main()
{
    int t = read();
    while (t--)
    {
        int n = read();
        set<tpl> s;
        for (int i = 0; i < n; i++)
        {
            int k, x;
            k = read();
            while (k--)
                x = read(), vec[i].push_back(x);
            sort(vec[i].begin(), vec[i].end());
            s.emplace(vec[i].front(), i, 0);
        }
        int ans = get<0>(*s.rbegin()) - get<0>(*s.begin());
        while (1)
        {
            auto [mn, mnid, mnpos] = *s.begin();
            if (mnpos + 1 == vec[mnid].size())
                break;
            s.erase(s.begin());
            ++mnpos;
            s.emplace(vec[mnid][mnpos], mnid, mnpos);
            ans = min(ans, get<0>(*s.rbegin()) - get<0>(*s.begin()));
        }
        write(ans), putchar('\n');
        for (int i = 0; i < n; i++)
            vec[i].clear();
    }
    flush();

    return 0;
}

D. Data Generation

数学

队友写的。
每个位置不等于本身的概率相同,只需考虑一个数,最后乘上 n n n。如果本身就是 i i i,变化一次后不是 i i i 的概率为 2 ∗ ( n − 1 ) / n 2 2*(n-1)/n^2 2(n1)/n2;如果本身不是 i i i,变化一次后变为 i i i 的概率是 2 / n 2 2/n^2 2/n2。另外两种概率用 1 1 1 减去即可。使用矩阵快速幂加速递推。

#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
constexpr i64 P = 998244353;
i64 qpow(i64 x,i64 y)
{
    i64 ret = 1LL;
    while(y)
    {
        if(y&1LL)
        {
            ret = ret * x % P;
        }
        y /= 2;
        x = x * x % P;
    }
    return ret;
}
class mat
{
public:
    static constexpr int maxsz = 2;
    i64 a[maxsz][maxsz];
    int sz = 0;
    mat(){};
    mat(int sz)
    {
        for (int i = 0; i < sz; i++)
        {
            for (int j = 0; j < sz; j++)
            {
                a[i][j] = 0;
            }
        }
        this->sz = sz;
    }
    void init()
    {
        for (int i = 0; i < this->sz; i++)
        {
            for (int j = 0; j < this->sz; j++)
            {
                a[i][j] = (i == j);
            }
        }
    }
    mat operator-(const mat &rhs) const
    {
        mat res(this->sz);
        for (int i = 0; i < sz; i++)
        {
            for (int j = 0; j < sz; j++)
            {
                res.a[i][j] = (a[i][j] - rhs.a[i][j]) % P;
            }
        }
        return res;
    }
    mat operator+(const mat &rhs) const
    {
        mat res(this->sz);
        for (int i = 0; i < sz; i++)
        {
            for (int j = 0; j < sz; j++)
            {
                res.a[i][j] = (a[i][j] + rhs.a[i][j]) % P;
            }
        }
        return res;
    }
    mat operator*(const mat &rhs) const
    {
        mat res(this->sz);
        i64 tmp;
        for (int i = 0; i < sz; i++)
        {
            for (int k = 0; k < sz; k++)
            {
                tmp = a[i][k];
                for (int j = 0; j < sz; j++)
                {
                    res.a[i][j] += rhs.a[k][j] * tmp % P;
                    res.a[i][j] %= P;
                }
            }
        }
        return res;
    }
    mat operator^(i64 y) const
    {
        mat res(this->sz), bas(this->sz);
        res.init();
        for (int i = 0; i < sz; i++)
        {
            for (int j = 0; j < sz; j++)
            {
                bas.a[i][j] = a[i][j] % P;
            }
        }
        while (y)
        {
            if (y & 1LL)
            {
                res = res * bas;
            }
            bas = bas * bas;
            y /= 2;
        }
        return res;
    }
};
mat bas;
void solve()
{
    i64 n, m;
    cin >> n >> m;
    bas = mat(2);
    n %= P;
    bas.a[0][1] = 2 * (n - 1) % P * qpow(n %P* n % P, P - 2) % P;
    bas.a[0][0] = (1 - bas.a[0][1] + P)%P;
    bas.a[1][0] = 2 * qpow(n %P* n % P, P - 2) % P;
    bas.a[1][1] = (1 - bas.a[1][0] + P) % P;
    bas = bas ^ m;
    i64 res = bas.a[0][1] * n % P;
    cout << (res%P+P)%P << "\n";
}
int main()
{
    cin.tie(0)->sync_with_stdio(0);
    i64 T = 1;
    cin >> T;
    while(T--)
    {
        solve();
    }
    return 0;
}

E. Teyvat

图论DP

在简单无向图上求最短路径能覆盖给定点集的点对数量,相似类别的问题都是广义圆方树的经典应用。

对于点集大小 k k k,分类讨论:

  • k = 1 k=1 k=1,DP预处理出经过每个点的圆点点对数即可 O ( 1 ) O(1) O(1) 回答
  • k > 1 k>1 k>1,所有点形成一条自底向上的链,记 s z [ v ] sz[v] sz[v] v v v 子树中圆点个数, v , u v,u v,u 分别是链顶、链尾, w w w v v v 在圆方树上对应的下一个点,答案为 s z [ u ] ∗ ( n − s z [ w ] ) sz[u]*(n-sz[w]) sz[u](nsz[w])
  • k > 1 k>1 k>1,所有点形成一条倒V型链,记 v , u v,u v,u 分别为链两端,答案为 s z [ v ] ∗ s z [ u ] sz[v]*sz[u] sz[v]sz[u]
  • k > 1 k>1 k>1,不是以上情况,即不能被一条路径覆盖,答案为 0 0 0

DP处理参考第二场Counter Strike。判断是否是链状,一种简单实现是使用虚树,如果只有LCA出度为2,或者没有点出度为2,就是链状。

略卡常,还卡内存,需要快读。

#include <bits/stdc++.h>
using namespace std;

constexpr int MAXN = 1e6 + 5;
int low[MAXN], num[MAXN], dfn, nn, n, m, q;
int dep[MAXN], father[MAXN][20], lg[MAXN], sz[MAXN];
int stk[MAXN], tp, cnt, leaf1, leaf2;
int64_t dp[MAXN];
vector<int> G[MAXN], T[MAXN], ver;
stack<int> st;

namespace fastIO
{
#ifndef LOCAL
#define getchar() p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin), p1 == p2) ? EOF : *p1++
#define putchar(x) (p3 - obuf < 1000000) ? (*p3++ = x) : (fwrite(obuf, p3 - obuf, 1, stdout), p3 = obuf, *p3++ = x)
    char buf[1000000], *p1 = buf, *p2 = buf, obuf[1000000], *p3 = obuf;
    inline void flush() { fwrite(obuf, p3 - obuf, 1, stdout); }
#endif
    inline int read()
    {
        int s = 0;
        char ch = getchar();
        while (ch < '0' || ch > '9')
            ch = getchar();
        while (ch >= '0' && ch <= '9')
            s = s * 10 + ch - '0', ch = getchar();
        return s;
    }
    inline void write(long long x)
    {
        if (x > 9)
            write(x / 10);
        putchar(x % 10 + '0');
    }
};
using namespace fastIO;

void tarjan(int v)
{
    low[v] = num[v] = ++dfn;
    st.push(v);
    for (auto u : G[v])
    {
        if (!num[u])
        {
            tarjan(u);
            low[v] = min(low[u], low[v]);
            if (low[u] >= num[v])
            {
                ++nn;
                int z;
                do
                {
                    z = st.top(), st.pop();
                    T[z].push_back(nn), T[nn].push_back(z);
                } while (z != u);
                T[v].push_back(nn), T[nn].push_back(v);
            }
        }
        else
            low[v] = min(low[v], num[u]);
    }
}

int lca(int x, int y)
{
    if (dep[x] < dep[y])
        swap(x, y);
    while (dep[x] > dep[y])
        x = father[x][lg[dep[x] - dep[y]] - 1];
    if (x == y)
        return x;
    for (int k = lg[dep[x]] - 1; k >= 0; k--)
        if (father[x][k] != father[y][k])
            x = father[x][k], y = father[y][k];
    return father[x][0];
}

int jump(int v, int u)
{
    int d = dep[v] - dep[u] - 1;
    for (int i = 19; i >= 0; i--)
        if (d & (1 << i))
            v = father[v][i];
    return v;
}

void dfs1(int v, int fa)
{
    sz[v] = (v <= n);
    dp[v] = 0;
    num[v] = ++dfn;
    father[v][0] = fa;
    dep[v] = dep[fa] + 1;
    for (int i = 1; i < 20; i++)
        father[v][i] = father[father[v][i - 1]][i - 1];
    for (auto u : T[v])
    {
        if (u == fa)
            continue;
        dfs1(u, v);
        dp[v] += 1ll * sz[v] * sz[u];
        sz[v] += sz[u];
    }
    dp[v] += 1ll * sz[v] * (n - sz[v]);
}

int build(vector<int> &p)
{
    sort(p.begin(), p.end(), [](int v, int u)
         { return num[v] < num[u]; });
    auto root = p[0];
    for (int i = 1; i < p.size(); i++)
        root = lca(root, p[i]);
    stk[tp = 1] = root;
    G[root].clear();
    for (auto v : p)
    {
        if (v != root)
        {
            auto LCA = lca(v, stk[tp]);
            if (LCA != stk[tp])
            {
                while (num[LCA] < num[stk[tp - 1]])
                    G[stk[tp - 1]].push_back(stk[tp]), --tp;
                if (num[LCA] > num[stk[tp - 1]])
                    G[LCA].clear(), G[LCA].push_back(stk[tp]), stk[tp] = LCA;
                else
                    G[LCA].push_back(stk[tp--]);
            }
            G[v].clear(), stk[++tp] = v;
        }
    }
    for (int i = 1; i < tp; i++)
        G[stk[i]].push_back(stk[i + 1]);
    return root;
}

void dfs2(int v)
{
    if (G[v].size() >= 2)
        ++cnt;
    else if (G[v].size() == 0)
        leaf1 == 0 ? leaf1 = v : leaf2 = v;
    for (auto u : G[v])
        dfs2(u);
}

int main()
{
    int size(256 << 20);
    __asm__("movq %0, %%rsp\n" ::"r"((char *)malloc(size) + size));
    for (int i = 1; i < MAXN; i++)
        lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
    ver.reserve(1000000);
    int t = read();
    while (t--)
    {
        n = read(), m = read(), q = read(), nn = n;
        while (m--)
        {
            int v = read(), u = read();
            G[v].push_back(u), G[u].push_back(v);
        }
        tarjan(1);
        for (int i = 1; i <= n; i++)
            G[i].clear();
        dfn = 0;
        dfs1(1, 0);
        while (q--)
        {
            int k = read(), x;
            for (int i = 0; i < k; i++)
                x = read(), ver.push_back(x);
            if (k == 1)
                write(dp[ver[0]] + 1), putchar('\n'); // S=T也算
            else
            {
                auto rt = build(ver);
                cnt = leaf1 = leaf2 = 0;
                dfs2(rt);
                if (cnt == 1 && G[rt].size() == 2)
                    write(1ll * sz[leaf1] * sz[leaf2]), putchar('\n');
                else if (cnt == 0)
                    write(1ll * sz[leaf1] * (n - sz[jump(leaf1, rt)])), putchar('\n');
                else
                    putchar('0'), putchar('\n');
            }
            ver.clear();
        }

        dfn = 0;
        fill(num + 1, num + nn + 1, 0);
        for (int i = 1; i <= nn; i++)
            G[i].clear(), T[i].clear();
    }
    flush();

    exit(0);
}

F. PSO

数学

显然最大距离只在 n = 2 n=2 n=2 时为 1 1 1,其余情况均为 2 2 2
期望距离分类讨论,距离为 2 2 2 的点对有 C n − 1 2 C_{n-1}^{2} Cn12 对,距离为 1 1 1 的点对有 n − 1 n-1 n1 对,答案为 C n − 1 2 ∗ 2 + ( n − 1 ) ∗ 1 C n 2 = 2 − 2 n \cfrac{C_{n-1}^{2}*2+(n-1)*1}{C_n^2}=2-\cfrac{2}{n} Cn2Cn122+(n1)1=2n2

#include <bits/stdc++.h>
using namespace std;

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cout << fixed << setprecision(9);
    int t;
    cin >> t;
    while (t--)
    {
        int n;
        cin >> n;
        if (n == 2)
            cout << 1.0 << " " << 1.0 << "\n";
        else
            cout << 2.0 - 2.0 / n << " " << 2.0 << "\n";
    }

    return 0;
}

G. Guess

数学暴力

给队友了。主要问题在于打表找规律,以及注意Pollard-Rho判素数的时间效率。

#include <bits/stdc++.h>
using i64 = long long;
using ld = long double;
constexpr i64 N = 1e5;
constexpr i64 P = 998244353;
using namespace std;
i64 gcd(i64 a, i64 b)
{
    return b == 0 ? a : gcd(b, a % b);
}
i64 qpow(i64 x, i64 y, i64 P)
{
    i64 ret = 1LL;
    while (y)
    {
        if (y & 1LL)
        {
            ret = (__int128)ret * x % P;
        }
        y /= 2;
        x = (__int128)x * x % P;
    }
    return ret;
}
bool is_prime(i64 x)
{
    if (x < 3)
    {
        return x == 2;
    }
    if (x % 2 == 0)
    {
        return 0;
    }
    i64 d = x - 1, r = 0;
    while (d % 2 == 0)
    {
        d /= 2;
        ++r;
    }
    i64 A[] = {2, 325, 9375, 28178, 450775, 9780504, 1795265022};
    for (auto &a : A)
    {
        i64 v = qpow(a, d, x);
        if (v <= 1 || v == x - 1)
        {
            continue;
        }
        for (i64 i = 0; i < r; ++i)
        {
            v = (__int128)v * v % x;
            if (v == x - 1 && i != r - 1)
            {
                v = 1;
                break;
            }
            if (v == 1)
            {
                return 0;
            }
        }
        if (v != 1)
        {
            return false;
        }
    }
    return true;
}

template <class T>
T randint(T l, T r = 0)
{
    static mt19937 eng(time(0));
    if (l > r)
    {
        swap(l, r);
    }
    uniform_int_distribution<T> dis(l, r);
    return dis(eng);
}
i64 Pollard_Rho(i64 N)
{
    if (N == 4)
    {
        return 2;
    }
    if (is_prime(N))
    {
        return N;
    }
    while (1)
    {
        i64 c = randint(1LL, N - 1);
        auto f = [=](i64 x)
        {
            return ((__int128)x * x + c) % N;
        };
        i64 t = 0, r = 0, p = 1, q;
        do
        {
            for (i64 i = 0; i < 128; ++i)
            {
                t = f(t), r = f(f(r));
                if (t == r || (q = (__int128)p * abs(t - r) % N) == 0)
                {
                    break;
                }
                p = q;
            }
            i64 d = gcd(p, N);
            if (d > 1)
            {
                return d;
            }
        } while (t != r);
    }
}
std::unordered_map<i64, i64> mp;
i64 mx = 1e18;
void find(i64 n)
{
    if (n == 1)
    {
        return;
    }
    if (is_prime(n))
    {
        mx = std::min(mx, n);
        mp[n]++;
        return;
    }
    i64 p = n;
    while (p >= n)
    {
        p = Pollard_Rho(p);
    }
    find(p);
    find(n / p);
}
void solve()
{
    i64 n;
    i64 m;
    cin >> n;
    m = n;
    if (is_prime((n)))
    {
        cout << n % P << " ";
    }
    else
    {
        mp.clear();
        mx = 1e18 + 10;
        while (n > 1)
        {
            i64 tmp = Pollard_Rho(n);
            if (is_prime(tmp))
            {
            	mx=std::min(mx,tmp);
                mp[tmp]++;
            }
            else
            {
                find(tmp);
            }
            n /= tmp;
        }
        bool ok1 = 1;
        for (auto &e : mp)
        {
            if (e.second >= 2)
            {
                ok1 = 0;
                break;
            }
        }
        bool ok2 = (mp.size() > 1);
        if (ok1)
        {
            cout << "1 ";
        }
        else if (!ok1 && ok2)
        {
            cout << "1 ";
        }
        else if (!ok1 && !ok2)
        {
            cout << mx % P << " ";
        }
    }
}
int main()
{
    i64 T = 1;
    cin.tie(0)->sync_with_stdio(0);
    cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}

H. String and GCD

字符串数学

莫反+fail树,队友写的。

#include<bits/stdc++.h>
using namespace std;

const int N=1e6+5;
const int MOD=998244353;
int t,n,m,tot,ans;
int p[N+5],nex[N+5],cnt[N+5];
char s[N+5];
vector<int> yz[N+5],G[N+5];
bool not_p[N+5];
int prime[N + 5],phi[N+5];

void get_phi()
{
    phi[1]=1;
	for (int i = 2; i <= N;i++)
    {
        if(!not_p[i])
        {
            prime[++tot] = i;
            phi[i]=i-1;
        }
        for (int j = 1; j <= tot && i * prime[j] <= N;j++)
        {
            not_p[prime[j] * i] = 1;
            if(i%prime[j]==0)
            {
                phi[i*prime[j]]=phi[i]*prime[j];
                break;
            }
            phi[i*prime[j]]=phi[i]*(prime[j]-1);
        }
    }
}

void getYz()
{
	for(int i=1;i<=N;i++)
		for(int j=i;j<=N;j+=i)
			yz[j].push_back(i);
}
void csh()
{
	ans=1;
	for(int i=0;i<=n;i++)
	{
		nex[i]=0;
		G[i].clear();
	}
}
void dfs(int v)
{
	int sum=1;
	for(int i=0;i<yz[v].size();i++)
	{
		int x=yz[v][i];
		sum=(sum+1ll*cnt[x]*phi[x])%MOD;
		cnt[x]++;
	}	
	ans=(1ll*ans*sum)%MOD;
	for(int i=0;i<G[v].size();i++)
		dfs(G[v][i]);
	
	for(int i=0;i<yz[v].size();i++)
		cnt[yz[v][i]]--;
}

int main() 
{
	int size(512<<20); // 512M
	__asm__ ( "movq %0, %%rsp\n"::"r"((char*)malloc(size)+size));
	get_phi();
	getYz();
	cin>>t;
	while(t--)
	{
		csh();
		scanf("%s",s+1);
		n=strlen(s+1);
		int j=0;
		for(int i=2;i<=n;i++)
		{
			while(j&&s[i]!=s[j+1]) j=nex[j];
			if(s[i]==s[j+1])       j++;
			nex[i]=j;
		}
		for(int i=1;i<=n;i++)
		G[nex[i]].push_back(i);
		dfs(0);
		printf("%lld\n",ans);		
	}
	
	exit(0);
}

I. WO MEI K

数学图论数据结构

先考虑 k = 2 k=2 k=2 的情况,可以直接考虑每条边对答案的贡献,对一条边的两个端点,分别向外扩展直至遇到同边权的边对应的端点为止,所得的连通块大小分别为 s z [ v ] , s z [ u ] sz[v],sz[u] sz[v],sz[u],则该边贡献就是 s z [ v ] ∗ s z [ u ] sz[v]*sz[u] sz[v]sz[u]。显然每次考虑边权为 w w w 的所有边时,相当于在原树上抠除这些边,形成若干个连通块,只要能维护连通块大小,就可以计算每条边的贡献。

记所有边贡献之和为 r e s res res,则对于所有 k ≥ 2 k \geq 2 k2,对应结果为 r e s ∗ C n − 2 k − 2 C n k \cfrac{res*C_{n-2}^{k-2}}{C_n^k} CnkresCn2k2,其中乘上 C n − 2 k − 2 C_{n-2}^{k-2} Cn2k2 是因为选定的边在上述 C n − 2 k − 2 C_{n-2}^{k-2} Cn2k2 种组合中都提供贡献。最终答案是所有结果的异或和(给结果取模,但不要给最终答案取模)。

官方题解做法不再解释。这里提供一种使用LCT的做法,因为维护动态树的连通块大小是LCT的经典应用之一。这一做法需要知道如何使用LCT维护子树信息,参考P4219 [BJOI2014]大融合,维护方法完全一样。时间复杂度 O ( N l o g N ) O(NlogN) O(NlogN),但常数较大,使用快读可以卡过。

具体而言,因为通常的LCT只维护了实链上的信息 s z 1 [ v ] sz1[v] sz1[v],要想完整维护子树信息,我们可以增加一个 s z 2 [ v ] sz2[v] sz2[v] 记录虚儿子传来的信息,用 s z 1 [ v ] sz1[v] sz1[v] 记录完整子树信息。
于是所有涉及虚实儿子变化的函数都要对应修改,修改pushup、access、link即可,cut只会断开实链,无需修改。具体方法见代码。

#include <bits/stdc++.h>
using namespace std;

namespace fastIO
{
#ifndef LOCAL
#define getchar() p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin), p1 == p2) ? EOF : *p1++
    char buf[1000000], *p1 = buf, *p2 = buf;
#endif
    inline int read()
    {
        int s = 0;
        char ch = getchar();
        while (ch < '0' || ch > '9')
            ch = getchar();
        while (ch >= '0' && ch <= '9')
            s = s * 10 + ch - '0', ch = getchar();
        return s;
    }
};
using namespace fastIO;

#define ls tr[x].ch[0]
#define rs tr[x].ch[1]
constexpr int64_t MAXN = 2e5 + 5, mod = 998244353;
struct LCT
{
    struct Node
    {
        int fa, ch[2], lazy;
        int sz1, sz2;
    } tr[MAXN];

    bool isRoot(int x)
    {
        int g = tr[x].fa;
        return tr[g].ch[0] != x && tr[g].ch[1] != x;
    }
    void reverse(int x)
    {
        if (!x)
            return;
        swap(ls, rs);
        tr[x].lazy ^= 1;
    }
    void pushup(int x) { tr[x].sz1 = tr[ls].sz1 + tr[rs].sz1 + tr[x].sz2 + 1; }
    void pushdown(int x)
    {
        if (tr[x].lazy)
        {
            reverse(ls);
            reverse(rs);
            tr[x].lazy = 0;
        }
    }
    void push(int x)
    {
        if (!isRoot(x))
            push(tr[x].fa);
        pushdown(x);
    }
    void rotate(int x)
    {
        int y = tr[x].fa, z = tr[y].fa, k = tr[y].ch[1] == x;
        if (!isRoot(y))
            tr[z].ch[tr[z].ch[1] == y] = x;
        tr[x].fa = z;
        tr[y].ch[k] = tr[x].ch[k ^ 1];
        if (tr[x].ch[k ^ 1])
            tr[tr[x].ch[k ^ 1]].fa = y;
        tr[y].fa = x;
        tr[x].ch[k ^ 1] = y;
        pushup(y);
    }
    void splay(int x)
    {
        push(x);
        while (!isRoot(x))
        {
            int y = tr[x].fa, z = tr[y].fa;
            if (!isRoot(y))
                (tr[z].ch[0] == y) ^ (tr[y].ch[0] == x) ? rotate(x) : rotate(y);
            rotate(x);
        }
        pushup(x);
    }
    void access(int x)
    {
        for (int ch = 0; x; ch = x, x = tr[x].fa)
        {
            splay(x);
            tr[x].sz2 += tr[rs].sz1 - tr[ch].sz1;
            rs = ch;
            pushup(x);
        }
    }
    void makeroot(int x) { access(x), splay(x), reverse(x); }
    void split(int x, int y) { makeroot(x), access(y), splay(y); }
    void link(int x, int y)
    {
        makeroot(x);
        makeroot(y);
        tr[y].sz2 += tr[x].sz1;
        tr[x].fa = y;
        pushup(y);
    }
    void cut(int x, int y)
    {
        split(x, y);
        if (tr[y].ch[0] != x || rs)
            return;
        tr[x].fa = tr[y].ch[0] = 0;
        pushup(x);
    }
    int findroot(int x)
    {
        access(x);
        splay(x);
        while (ls)
            pushdown(x), x = ls;
        return x;
    }
} t1;

int64_t fac[MAXN];
vector<pair<int, int>> E[MAXN];

int64_t quickpow(int64_t a, int64_t b = mod - 2, int64_t m = mod)
{
    int64_t ans = 1;
    while (b > 0)
    {
        if (b & 1)
            ans = ans * a % m;
        a = a * a % m;
        b >>= 1;
    }
    return ans % m;
}

inline int64_t C(int n, int m) { return fac[n] * quickpow(fac[m]) % mod * quickpow(fac[n - m]) % mod; }

int main()
{
    fac[0] = 1;
    for (int64_t i = 1; i < MAXN; i++)
        fac[i] = fac[i - 1] * i % mod;
    int t = read();
    while (t--)
    {
        int n = read();
        memset(t1.tr, 0, sizeof(LCT::Node) * (n + 1));
        unordered_set<int> s;
        for (int i = 1; i < n; i++)
        {
            int v = read(), u = read(), w = read();
            E[w].emplace_back(v, u), s.insert(w);
            t1.link(v, u);
        }

        int64_t ans = 0, res = 0;
        for (auto w : s)
        {
            auto &e = E[w];
            for (auto [v, u] : e)
                t1.cut(v, u);
            for (auto [v, u] : e)
            {
                t1.makeroot(v);
                int64_t sz1 = t1.tr[v].sz1;
                t1.makeroot(u);
                int64_t sz2 = t1.tr[u].sz1;
                res = (res + sz1 * sz2 % mod) % mod;
            }
            for (auto [v, u] : e)
                t1.link(v, u);
            e.clear();
        }
        for (int k = 2; k <= n; k++)
            ans ^= res * C(n - 2, k - 2) % mod * quickpow(C(n, k)) % mod;
        printf("%lld\n", ans);	// 没要求给ans取模
    }

    return 0;
}

J. Kong Ming Qi

搜索暴力

打表,发现规律比较明显。唯一不清楚的只有 n = m = 4 n=m=4 n=m=4 的情况,手动模拟得到答案为1。于是可以猜测如果 n , m n,m n,m 若有一者能被 3 3 3 整除,则答案为2。只有一行或一列的情况需要特判。

N M Ans
1 1, 1
1 2, 1
1 3, 2
1 4, 2
1 5, 3
1 6, 3
2 1, 1
2 2, 1
2 3, 2
2 4, 1
2 5, 1
2 6, 2
3 1, 2
3 2, 2
3 3, 2
3 4, 2

#include <bits/stdc++.h>
using namespace std;

const int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int N, M, ans;
bool g[20][20];

void dfs()
{
    int cnt = 0;
    for (int i = 0; i < N + 2; i++)
    {
        for (int j = 0; j < M + 2; j++)
            if (g[i][j])
                ++cnt;
    }
    ans = min(ans, cnt);

    for (int i = 0; i < N + 2; i++)
    {
        for (int j = 0; j < M + 2; j++)
        {
            if (!g[i][j])
                continue;
            for (int k = 0; k < 4; k++)
            {
                int nx = i + dx[k], ny = j + dy[k];
                if (nx < 0 || nx > N + 1 || ny < 0 || ny > M + 1 || !g[nx][ny])
                    continue;
                int tx = nx + dx[k], ty = ny + dy[k];
                if (tx < 0 || tx > N + 1 || ty < 0 || ty > M + 1 || g[tx][ty])
                    continue;
                g[i][j] = 0, g[nx][ny] = 0, g[tx][ty] = 1;
                dfs();
                g[i][j] = 1, g[nx][ny] = 1, g[tx][ty] = 0;
            }
        }
    }
}

void bf()
{
    for (N = 1; N <= 3; N++)
    {
        for (M = 1; M <= 6; M++)
        {
            if (N == 3 && M >= 5)
                break;
            ans = 100;
            memset(g, 0, sizeof(g));
            for (int i = 1; i <= N; i++)
                for (int j = 1; j <= M; j++)
                    g[i][j] = 1;
            dfs();
            cout << N << " " << M << ", " << ans << endl;
        }
    }
}

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    // bf();
    int t;
    cin >> t;
    while (t--)
    {
        int n, m;
        cin >> n >> m;
        if (n > m)
            swap(n, m);
        if (n == 1)
            cout << (m - 1) / 2 + 1 << "\n";
        else
            cout << (n % 3 == 0 || m % 3 == 0 ? "2\n" : "1\n");
    }

    return 0;
}

K. Circuit

图论DP

找有向图的最小环是一个经典问题,既可以用floyd在 O ( N 3 ) O(N^3) O(N3) 时间內解决,也可以用dijkstra在 O ( N ( N + M ) l o g N ) O(N(N+M)logN) O(N(N+M)logN) 时间內解决,参照本文。为了方便理解,我们使用dijkstra算法。

本题的关键在于计数。首要一点是去重,设当前枚举的起点为 s s s,我们在最短路过程中强制不走所有 u < s u<s u<s 的点,则每个环只在以最小环点为起点时被计算一次。
同时,在最短路过程中维护到当前点的方案数,则 d p [ s ] dp[s] dp[s] 就是当前起点对应的最小环数量。

#include <bits/stdc++.h>
using namespace std;
#define int int64_t

constexpr int MAXN = 505, INF = 1e17, mod = 998244353;
using PII = pair<int, int>;
vector<PII> G[MAXN];
int d[MAXN], dp[MAXN], n, m;
bool vis[MAXN];

void dijkstra(int s)
{
    fill(vis + s, vis + n + 1, 0);
    fill(d + s, d + n + 1, INF);
    d[s] = 0, dp[s] = 1;
    priority_queue<PII, vector<PII>, greater<PII>> q;
    q.emplace(0, s);
    bool flg = 1;
    while (!q.empty())
    {
        auto [_, v] = q.top();
        q.pop();
        if (vis[v])
            continue;
        vis[v] = 1;

        for (auto [u, w] : G[v])
        {
            if (u < s)
                continue;
            if (!vis[u] && d[v] + w <= d[u])
            {
                if (d[v] + w < d[u])
                    d[u] = d[v] + w, dp[u] = dp[v], q.emplace(d[u], u);
                else
                    dp[u] = (dp[v] + dp[u]) % mod;
            }
        }

        if (flg)
            d[s] = INF, vis[s] = 0, flg = 0;
    }
}

int32_t main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t;
    cin >> t;
    while (t--)
    {
        cin >> n >> m;
        for (int i = 1; i <= m; i++)
        {
            int v, u, w;
            cin >> v >> u >> w;
            G[v].emplace_back(u, w);
        }
        int mn = 1e17, ways = 0;
        for (int i = 1; i <= n; i++)
        {
            dijkstra(i);
            if (d[i] < mn)
                mn = d[i], ways = dp[i];
            else if (d[i] == mn)
                ways = (ways + dp[i]) % mod;
        }
        if (mn == 1e17)
            cout << "-1 -1\n";
        else
            cout << mn << " " << ways << "\n";

        for (int i = 1; i <= n; i++)
            G[i].clear();
    }

    return 0;
}

L. a-b Problem

博弈论排序贪心

a + b a+b a+b 排序后从大到小轮流取即可。

#include <bits/stdc++.h>
using namespace std;

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t;
    cin >> t;
    while (t--)
    {
        int n;
        cin >> n;
        priority_queue<tuple<int, int, int>> q;
        for (int i = 1, a, b; i <= n; i++)
        {
            cin >> a >> b;
            q.emplace(a + b, a, b);
        }
        int64_t ans1 = 0, ans2 = 0;
        bool flg = 1;
        while (!q.empty())
        {
            auto [_, a, b] = q.top();
            q.pop();
            flg ? ans1 += a : ans2 += b;
            flg ^= 1;
        }
        cout << ans1 - ans2 << "\n";
    }

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NoobDream_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值