性质题
2025-11-24
暂无标签

一身素青纱 ⚡ 草柄当头花 ⚡


A - Maximum Diameter

https://www.luogu.com.cn/problem/AT_abc290_f

  • 考虑一个固定的序列,很容易发现可以用叶子来调整,使得树成为毛毛虫。

    故最长直径为 \(n-cnt_1+2\),其中 \(cnt_1\) 为叶子个数。

  • 发现只需要 \(\forall\,i,a_i>0\)\(\sum a_i=2n-2\),那么就有解。枚举叶子个数 \(k\) 再插板法算方案,得到答案为:

    \[ \sum_{k=2}^{n-1} \binom{2n-2-k-(n-k)-1}{n-k-1}\times \binom nk\times (n-k+1) \]

    BB:注意边数比点数少 1。

  • 题目没保证 \(\sum n\) 的范围,系数会变也不太方便预处理,考虑变形:

    \[ \begin{aligned} &\sum_{k=2}^{n-1} \binom{n-3}{k-2}\times \binom n{k}\times (n-k+1)\\ =&(n+1)\sum_{k=2}^{n-1} \binom{n-3}{k-2}\times \binom n{k}-\sum_{k=2}^{n-1} k\times \binom n{k}\times \binom{n-3}{k-2}\\ =&(n+1)\binom{2n-3}{n-2}-n\times \sum_{k=2}^{n-1} \binom {n-1}{k-1}\times \binom{n-3}{k-2}\\ =&(n+1)\times\binom{2n-3}{n-2}-n\times\binom{2n-4}{n-2}\\ \end{aligned} \]

    用到了:\(k\cdot C_n^k=n\cdot C_{n-1}^{k-1}\)、范德蒙德卷积 \(\sum_{i=0}^k C_n^i\cdot C_m^{k-i}=C_{n+m}^k\)

    只想说这种只涉及到高中组合知识,且放到高考数学卷子里我都拿不到分的式子就不要拿来考我了 :-)

#include <bits/stdc++.h>
const int mod = 998244353;
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
    const auto stime = std::chrono::steady_clock::now();
#endif
    const int N = 2e6;
    std::vector<long long> fac(N + 1), inv(N + 1);
    fac[0] = inv[0] = 1ll;
    for (int i = 1; i <= N; ++i)
        fac[i] = fac[i - 1] * i % mod;
    auto qkp = [&](long long x, int y) {
        auto res = 1ll;
        for (; y; (x *= x) %= mod, y >>= 1)
            if (y & 1)
                (res *= x) %= mod;
        return res;
    };
    inv[N] = qkp(fac[N], mod - 2);
    for (int i = N - 1; i; --i)
        inv[i] = inv[i + 1] * (i + 1) % mod;
    auto C = [&](int n, int m) {
        return fac[n] * inv[m] % mod * inv[n - m] % mod;
    };
    int T;
    for (std::cin >> T; T--; ) {
        int n;
        std::cin >> n;
        std::cout << ((n + 1) * C(2 * n - 3, n - 2) + mod - n * C(2 * n - 4, n - 2) % mod) % mod << '\n';
    }
#ifndef ONLINE_JUDGE
    std::cerr << std::fixed << std::setprecision(6) << std::chrono::duration<double> (std::chrono::steady_clock::now() - stime).count() << "s\n";
#endif
    return 0;
}

B - Game on Graph

https://www.luogu.com.cn/problem/AT_abc261_h

merchant 统治全世界……

  • \(f_{u,0/1}\) 表示在 \(x\) 点且 A / B 执棋时的代价。则:

    \[ f_{u,0}\gets \min\{f_{v,1}+w\}\\ f_{u,1}\gets \max\{f_{v,0}+w\} \]

  • 容易发现这大概是一个 Dij 的结构,对于出度为 \(0\) 的点 \(u\)\(f_{u,0}=f_{u,1}=0\),且出度为 \(0\) 恰好符合拓扑序,可以参照 merchant 中的处理方式,以 \(f_{*,0}\) 为主体做最短路 Dij,待 \(f_{*,1}\) 的出边将其更新完后加入优先队列(容易发现这个拓扑结构让它只会入队一次)。

    虽然过程很简单,但是思想很巧妙。总之可以看一看,结合 merchant 思考一下。

#include <bits/stdc++.h>
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
    const auto stime = std::chrono::steady_clock::now();
#endif
    const long long inf = 1e18;
    int n, m, s;
    std::cin >> n >> m >> s;
    std::vector<int> deg(n + 1);
    std::vector<std::vector<std::pair<int, int> > > g(n + 1);
    for (int x, y, w; m--; ) {
        std::cin >> x >> y >> w;
        g[y].emplace_back(x, w);
        ++deg[x];
    }
    std::vector<int> vis(n + 1);
    std::vector<std::vector<long long> > dis(n + 1, std::vector<long long> (2));
    struct node {
        long long w;
        int k, x;
        bool operator< (const node &q) const {
            return w > q.w;
        }
    };
    std::priority_queue<node> q;
    for (int i = 1; i <= n; ++i)
        if (!deg[i]) {
            dis[i][0] = dis[i][1] = 0ll;
            q.push({ 0ll, 0, i });
            q.push({ 0ll, 1, i });
        }
        else
            dis[i][0] = inf;
    for (; !q.empty(); ) {
        auto [d, k, x] = q.top();
        q.pop();
        if (k == 0) {
            if (vis[x])
                continue;
            vis[x] = 1;
            for (auto [i, w] : g[x]) {
                dis[i][1] = std::max(dis[i][1], std::min(d + w, inf));
                if (!--deg[i])
                    q.push({ dis[i][1], 1, i });
            }
        }
        else
            for (auto [i, w] : g[x])
                if (dis[i][0] > d + w) {
                    dis[i][0] = d + w;
                    q.push({ dis[i][0], 0, i });
                }
    }
    if (dis[s][0] == inf)
        std::cout << "INFINITY\n";
    else
        std::cout << dis[s][0] << '\n';
#ifndef ONLINE_JUDGE
    std::cerr << std::fixed << std::setprecision(6) << std::chrono::duration<double> (std::chrono::steady_clock::now() - stime).count() << "s\n";
#endif
    return 0;
}

C - Vertex Pairs

https://www.luogu.com.cn/problem/CF2042E

全新做法

  • 有一个 naive 的想法:check 删去 \(n\) 后是否存在合法连通块。

    如果存在,则至少有一个元素(\(a_n\))只被一个连通块包含,那么答案连通块一定是这一个。

    否则,说明 \(n\) 必选——这种情况当且仅当 \(n\) 的每一个子树(父亲)都不包含所有值时出现。

  • 扩展一下,DFS 序 + 线段树统计信息(DS 大师自有办法),找到最后一个不必选的点,然后把它裂开,找到唯一合法连通块,抹掉被删掉的所有点的信息,重复这个过程即可,复杂度很对。

    至于怎么统计这样的信息,点是两两配对的,以 DFS 序记录每一对 \((l, r)\),对于 \(x\) 的子树,其存在不包含的值当且仅当,DFS 序区间落在 \([1,l)\),或 \((l, r)\),或 \((r+1,n)\)。维护每个 \(i\) 作为左端点,需求的最小右端点即可。

    对于父亲方向的连通块,其存在不包含的值当且仅当,有某个 \([l,r]\) 完全落在 \(x\) 内。同样是好维护的。

  • 这样就获得了 \(O(n\log n)\) 的暴力 DS 做法。没有任何思维点!

#include <bits/stdc++.h>
const int maxn = 4e5 + 5;
struct {
    int l, r, u, mn, d;
} t[maxn << 2];
#define lt (p << 1)
#define rt (lt | 1)
void bld(int p, int l, int r) {
    t[p].mn = 0x3f3f3f3f;
    t[p].l = l, t[p].r = r;
    if (l == r)
        return;
    int mid = (l + r) >> 1;
    bld(lt, l, mid), bld(rt, mid + 1, r);
    return;
}
void pushdown(int p) {
    if (t[p].d) {
        t[lt].u = std::max(t[lt].u, t[p].d);
        t[lt].d = std::max(t[lt].d, t[p].d);
        t[rt].u = std::max(t[rt].u, t[p].d);
        t[rt].d = std::max(t[rt].d, t[p].d);
        t[p].d = 0;
    }
    return;
}
void add(int p, int x, int v) {
    t[p].mn = std::min(t[p].mn, v);
    if (t[p].l == t[p].r)
        return;
    int mid = (t[p].l + t[p].r) >> 1;
    if (x <= mid)
        add(lt, x, v);
    else
        add(rt, x, v);
    return;
}
void add(int p, int l, int r, int v) {
    if (l <= t[p].l && t[p].r <= r) {
        t[p].u = std::max(t[p].u, v);
        t[p].d = std::max(t[p].d, v);
        return;
    }
    pushdown(p);
    int mid = (t[p].l + t[p].r) >> 1;
    if (l <= mid)
        add(lt, l, r, v);
    if (r > mid)
        add(rt, l, r, v);
    t[p].u = std::max(t[lt].u, t[rt].u);
    return;
}
int asku(int p, int x) {
    if (t[p].l == t[p].r)
        return t[p].u;
    pushdown(p);
    int mid = (t[p].l + t[p].r) >> 1;
    if (x <= mid)
        return asku(lt, x);
    return asku(rt, x);
}
int askmn(int p, int l, int r) {
    if (l <= t[p].l && t[p].r <= r)
        return t[p].mn;
    int mid = (t[p].l + t[p].r) >> 1, res = 0x3f3f3f3f;
    if (l <= mid)
        res = askmn(lt, l, r);
    if (r > mid)
        res = std::min(res, askmn(rt, l, r));
    return res;
}
#undef lt
#undef rt
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
    const auto stime = std::chrono::steady_clock::now();
#endif
    int n;
    std::cin >> n;
    std::vector<int> a(2 * n + 1);
    for (int i = 1; i <= 2 * n; ++i)
        std::cin >> a[i];
    std::vector<std::vector<int> > g(2 * n + 1);
    for (int i = 1, x, y; i < 2 * n; ++i) {
        std::cin >> x >> y;
        g[x].push_back(y), g[y].push_back(x);
    }
    std::vector<int> dfn(2 * n + 1), rfn(2 * n + 1), fa(2 * n + 1), s(n + 1);
    std::function<void(int)> DFS = [&](int x) {
        static int now = 0;
        dfn[x] = ++now, s[a[x]] += now;
        for (auto i : g[x])
            if (i != fa[x])
                fa[i] = x, DFS(i);
        rfn[x] = now;
        return;
    };
    DFS(1);
    std::vector<int> la(n + 1);
    bld(1, 1, 2 * n);
    for (int i = 1; i <= 2 * n; ++i)
        if (la[a[i]]) {
            int l = dfn[la[a[i]]], r = dfn[i];
            if (l > r)
                std::swap(l, r);
            add(1, l, r);
            if (l != 1)
                add(1, 1, l - 1, l);
            if (l + 1 <= r - 1)
                add(1, l + 1, r - 1, r);
            if (r != 2 * n)
                add(1, r + 1, 2 * n, 0x3f3f3f3f);
        }
        else
            la[a[i]] = i;
    std::vector<int> del(2 * n + 1), res;
    for (int i = 2 * n; i; --i)
        if (!del[i]) {
            int to = 0;
            for (auto j : g[i])
                if (!del[j] && j != fa[i] && asku(1, dfn[j]) <= rfn[j]) {
                    to = j;
                    break;
                }
            if (!to && (!fa[i] || del[fa[i]] || askmn(1, dfn[i], rfn[i]) <= rfn[i]))
                res.push_back(i);
            else {
                std::function<void(int, int)> DFS = [&](int x, int fa) {
                    assert(s[a[x]] > dfn[x]);
                    s[a[x]] -= dfn[x];
                    add(1, s[a[x]], s[a[x]]);
                    if (s[a[x]] < dfn[x]) {
                        if (s[a[x]] != 2 * n)
                            add(1, s[a[x]] + 1, 2 * n, 0x3f3f3f3f);
                    }
                    else
                        add(1, 1, s[a[x]], s[a[x]]);
                    del[x] = 1;
                    for (auto i : g[x])
                        if (!del[i] && i != fa)
                            DFS(i, x);
                    return;
                };
                DFS(i, to ? to : fa[i]);
            }
        }
    std::cout << (int)res.size() << '\n';
    std::reverse(res.begin(), res.end());
    for (auto i : res)
        std::cout << i << ' ';
    std::cout << '\n';
#ifndef ONLINE_JUDGE
    std::cerr << std::fixed << std::setprecision(6) << std::chrono::duration<double> (std::chrono::steady_clock::now() - stime).count() << "s\n";
#endif
    return 0;
}

D - Tree and Hamilton Path

https://www.luogu.com.cn/problem/AT_agc018_d

很难

  • 如果问的是哈密顿回路,做法:

    枚举每条边 \((u, v)\)


言论