20250215

T1

树上路径划分计数

两种做法。

第一种是根据经典结论,若干数和为 \(S\),则这些数至多有 \(\mathcal{O}(\sqrt{n})\) 种。因此至多有根号种度数,每种分别做,复杂度 \(\mathcal{O}(\sqrt{n})\)。

第二种是每次取出所有度数 \(\ge x\) 的点建虚树跑 dp,乍一看会以为是虚树外单 \(\ln\)。但是再观察会发现每个点会被用到 \(\deg\) 次,因此总复杂度就是建虚树的复杂度。

代码
#include <iostream>
#include <algorithm>
#include <vector>
#define int long long
using namespace std;
const int P = 998244353;
int n;
int head[100005], nxt[200005], to[200005], ecnt;
void add(int u, int v) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt; }
int fa[100005], dfn[100005], _dfn[100005], dep[100005], ncnt;
int sz[100005], son[100005], top[100005];
void dfs1(int x, int d, int fa) {
    sz[x] = 1;
    dep[x] = d;
    ::fa[x] = fa;
    _dfn[dfn[x] = ++ncnt] = x;
    for (int i = head[x]; i; i = nxt[i]) {
        int v = to[i];
        if (v != fa) {
            dfs1(v, d + 1, x);
            sz[x] += sz[v];
            if (sz[v] > sz[son[x]]) 
                son[x] = v;
        }
    }
}
void dfs2(int x, int t) {
    top[x] = t;
    if (son[x]) 
        dfs2(son[x], t);
    for (int i = head[x]; i; i = nxt[i]) {
        int v = to[i];
        if (v != son[x] && v != fa[x]) 
            dfs2(v, v);
    }
}
int LCA(int x, int y) {
    while (top[x] ^ top[y]) (dep[top[x]] < dep[top[y]]) ? (y = fa[top[y]]) : (x = fa[top[x]]);
    return (dep[x] < dep[y] ? x : y);
}
int stk[100005], ssz;
int deg[100005], X;
namespace Vt {
    int head[100005], nxt[200005], to[200005], ecnt;
    void add(int u, int v) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt; }
    void work(vector<int> key) {
        ecnt = 0;
        sort(key.begin(), key.end(), [](int x, int y) { return dfn[x] < dfn[y]; });
        int kcnt = key.size();
        stk[ssz = 1] = 1;
        head[1] = 0;
        // cout << kcnt << " kcnt\n";
        for (int i = (key[0] == 1); i < kcnt; i++) {
            int t = LCA(stk[ssz], key[i]);
            head[key[i]] = 0;
            if (stk[ssz] != t) {
                while (ssz > 1 && dfn[t] < dfn[stk[ssz - 1]]) {
                    add(stk[ssz - 1], stk[ssz]);
                    --ssz;
                }
                if (t != stk[ssz - 1]) {
                    head[t] = 0;
                    add(t, stk[ssz]);
                    stk[ssz] = t;
                } else {
                    add(t, stk[ssz]);
                    --ssz;
                }
            }
            stk[++ssz] = key[i];
        }
        for (int i = ssz; i > 1; i--) add(stk[i - 1], stk[i]);
    }
    int dp[100005][3];
    void dfs(int x, int fa) {
        dp[x][0] = 1;
        dp[x][1] = 0;
        dp[x][2] = (deg[x] >= X);
        for (int i = head[x]; i; i = nxt[i]) {
            int v = to[i];
            if (v != fa) {
                dfs(v, x);
                int a = (dp[v][0] + (deg[v] >= X) * dp[v][1] + dp[v][2]) % P, b = ((deg[v] >= X) * dp[v][0] + dp[v][1]) % P;
                dp[x][2] = (dp[x][2] * a + dp[x][1] * b) % P;
                dp[x][1] = (dp[x][1] * a + dp[x][0] * b) % P;
                dp[x][0] = dp[x][0] * a % P;
            }
        }
    }
}
vector<int> vec;
signed main() {
    freopen("degree.in", "r", stdin);
    freopen("degree.out", "w", stdout);
    cin >> n;
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        add(u, v), ++deg[u];
        add(v, u), ++deg[v];
    }
    dfs1(1, 0, 1);
    dfs2(1, 1);
    for (int i = 1; i <= n; i++) vec.emplace_back(i);
    sort(vec.begin(), vec.end(), [](int x, int y) { return deg[x] > deg[y]; });
    for (X = 1; X < n; X++) {
        while (vec.size() && deg[vec.back()] < X) vec.pop_back();
        Vt::work(vec);
        Vt::dfs(1, 0);
        cout << (Vt::dp[1][0] + (deg[1] >= X) * Vt::dp[1][1] + Vt::dp[1][2]) % P << "\n";
    }
    return 0;
}

T2

HEDWIG

若区间合法,则答案为 \(\frac{\max - \min}{d} - r + l\)。先算出每个右端点最远的合法左端点,然后把所有数除以 \(d\)。然后变成给定区间,求所有合法子区间最大值之和。

先考虑离线,则可以使用单调栈和区间加、区间历史和线段树维护。强制在线就可持久化一下。合法左端点的限制只需要在扫到右端点的时候注意一下即可。

代码
#include <iostream>
#include <algorithm>
#include <string.h>
#include <vector>
#include <map>
#define int unsigned
using namespace std;
int n, d;
unsigned tp;
int lst[100005];
int dc[100005], dcnt, aa[100005];
int ap[100005];
struct info {
    int s, hs, len;
};
struct ltag {
    int tg, cu, us;
};
struct node {
    int l, r;
    info v;
    ltag tg;
} T[30000005];
inline info operator+(info a, info b) { return (info) { a.s + b.s, a.hs + b.hs, a.len + b.len }; }
inline info operator+(info a, ltag t) {
    a.hs += a.len * t.us + a.s * t.cu;
    a.s += a.len * t.tg;
    return a;
}
inline ltag operator+(ltag a, ltag t) {
    a.us += t.us + a.tg * t.cu;
    a.cu += t.cu;
    a.tg += t.tg;
    return a;
}
struct Segment_Tree {
    int ncnt;
    void tag(int o, ltag t) {
        T[o].v = T[o].v + t;
        T[o].tg = T[o].tg + t;
    }
    void pushdown(int p) {
        if (!T[p].tg.tg && !T[p].tg.cu && !T[p].tg.us) 
            return;
        node q = T[p];
        T[T[p].l = ++ncnt] = T[q.l];
        T[T[p].r = ++ncnt] = T[q.r];
        tag(T[p].l, T[p].tg);
        tag(T[p].r, T[p].tg);
        T[p].tg = (ltag) { 0, 0, 0 };
    }
    void pushup(int o) { T[o].v = T[T[o].l].v + T[T[o].r].v; }
    void Build(int& o, int l, int r) {
        o = ++ncnt;
        T[o].v.len = r - l + 1;
        if (l == r) 
            return;
        int mid = (l + r) >> 1;
        Build(T[o].l, l, mid);
        Build(T[o].r, mid + 1, r);
    }
    void Add(int &p, int l, int r, int L, int R, ltag t) {
        node tmp = T[p];
        T[p = ++ncnt] = tmp;
        if (L <= l && r <= R) 
            return tag(p, t);
        pushdown(p);
        int mid = (l + r) >> 1;
        if (L <= mid) 
            Add(T[p].l, l, mid, L, R, t);
        if (R > mid) 
            Add(T[p].r, mid + 1, r, L, R, t);
        pushup(p);
    }
    int Query(int o, int l, int r, int L, int R) {
        if (L <= l && r <= R) 
            return T[o].v.hs;
        pushdown(o);
        int mid = (l + r) >> 1;
        if (R <= mid) 
            return Query(T[o].l, l, mid, L, R);
        if (L > mid) 
            return Query(T[o].r, mid + 1, r, L, R);
        return Query(T[o].l, l, mid, L, R) + Query(T[o].r, mid + 1, r, L, R);
    }
} seg;
int stk1[100005], sz1;
int stk2[100005], sz2;
int ans[100005], rs[100005];
vector<pair<int, int> > vec[100005];
int a[100005], q;
int rt[100005];
signed main() {
    freopen("hedwig.in", "r", stdin);
    freopen("hedwig.out", "w", stdout);
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> d >> q >> tp;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= n;) {
        int j = i;
        while (j <= n && a[i] % d == a[j] % d) rs[j] = i, ++j;
        i = j;
    }
    for (int i = 1; i <= n; i++) a[i] /= d, dc[i] = a[i];
    sort(dc + 1, dc + n + 1);
    dcnt = unique(dc + 1, dc + n + 1) - dc - 1;
    for (int i = 1; i <= n; i++) aa[i] = lower_bound(dc + 1, dc + dcnt + 1, a[i]) - dc;
    for (int i = 1; i <= n; i++) lst[i] = ap[aa[i]], ap[aa[i]] = i;
    int l, r;
    unsigned lans = 0;
    seg.Build(rt[0], 1, n);
    for (int i = 1, cl = 1; i <= n; i++) {
        if (ap[aa[i]]) 
            cl = max(cl, lst[i] + 1);
        else 
            ap[aa[i]] = 1;
        cl = max(cl, rs[i]);
        rt[i] = rt[i - 1];
        if (cl != i) {
            while (sz1 && a[i] > a[stk1[sz1]]) seg.Add(rt[i], 1, n, max(cl, stk1[sz1 - 1] + 1), stk1[sz1], (ltag) { a[i] - a[stk1[sz1]], 0, 0 }), --sz1;
            while (sz2 && a[i] < a[stk2[sz2]]) seg.Add(rt[i], 1, n, max(cl, stk2[sz2 - 1] + 1), stk2[sz2], (ltag) { a[stk2[sz2]] - a[i], 0, 0 }), --sz2;
            if (i != 1) 
                seg.Add(rt[i], 1, n, cl, i - 1, (ltag) { (unsigned)-1, 0, 0 });
            seg.Add(rt[i], 1, n, cl, i, (ltag) { 0, 1, 0 });
        }
        stk1[++sz1] = i;
        stk2[++sz2] = i;
    }
    while (q--) {
        cin >> l >> r;
        l ^= (lans * tp), r ^= (lans * tp);
        cout << (lans = seg.Query(rt[r], 1, n, l, r)) << "\n";
    }
    return 0;
}

T3

猴子

考虑用完金坷垃之后,树上必然存在一个 \(a_i = 0\) 的点。则所有猴子都会在有限步之内来到这个子树,然后再也出不去。因此只需要考虑这棵子树内的情况。所有猴子都进入这个子树之后,就完全相同了,只需要考虑一只。

考虑二分答案,变成跳成功一次获得 \(1 - mid\) 的收益,否则获得 \(-mid\) 的收益,问最后收益是否非负。设 \(f_i\) 表示从 \(i\) 开始,第一次跳出 \(i\) 子树时期望获得了多少收益。转移方程只和子树有关,可以从下往上递推。因此对一个给定的根 \(x\) 做一次的复杂度关于 \(size_x\) 线性。由于数据随机,每个点的 \(size\) 之和是 \(\mathcal{O}(n \log n)\) 级别。因此枚举 \(x\) 后二分直接做即可做到双 \(\log\)。然后会发现二分时的 \(check(rt, mid)\) 能够在 \(\mathcal{O}(size_{rt})\) 的时间内检查 \(mid\) 是否合法,因此可以以随机顺序枚举 \(x\),先 \(check\) 对应的 \(rt\) 能否到达当前最优答案 \(ans\),若能再进去二分。由于随机数列的前缀最大值个数是期望 \(\log\),这样做的复杂度就变成 \(\mathcal{O}(n\log n + \log^2n\log V)\)。

代码
#include <iostream>
#include <algorithm>
#include <iomanip>
#include <random>
using namespace std;
const double eps = 1e-10;
random_device rd;
mt19937 mtrand(rd());
int n;
int head[500005], nxt[1000005], to[1000005], ecnt;
void add(int u, int v) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt; }
int o[500005], _a[500005], a[500005];
int sz[500005], fa[500005];
void dfs(int x) {
    sz[x] = 1;
    for (int i = head[x]; i; i = nxt[i]) dfs(to[i]), sz[x] += sz[to[i]];
}
int X, rt;
double s[500005], f[500005], e;
void dfs1(int x) {
    double p = ((a[x] + X) % n) / (n * 1.0);
    s[x] = 0;
    for (int i = head[x]; i; i = nxt[i]) {
        int v = to[i];
        dfs1(v);
        s[x] += s[v];
    }
    if (x != rt) {
        f[x] = 1 - e + (1 - p) / p * (-e + s[x] / sz[x]);
        s[x] += f[x] * sz[x];
    }
}
bool chk(int x, double mid) {
    rt = x, e = mid;
    dfs1(x);
    return s[rt] / sz[x] - e > 0;
}
int main() {
    freopen("tree.in", "r", stdin);
    freopen("tree.out", "w", stdout);
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> fa[i], add(fa[i], i);
    for (int i = 1; i <= n; i++) cin >> a[i], _a[a[i]] = i, o[i] = i - 1;
    dfs(1);
    shuffle(o + 1, o + n + 1, mtrand);
    double ans = 0;
    for (int i = 1; i <= n; i++) {
        X = o[i];
        int x = _a[(n - X) % n];
        if (chk(x, ans)) {
            double l = ans, r = 1, mid;
            while (r - l > eps) {
                mid = (l + r) / 2;
                if (chk(x, mid)) 
                    ans = mid, l = mid;
                else 
                    r = mid;
            }
        }
    }
    cout << fixed << setprecision(10) << ans << "\n";
    return 0;
}
posted @ 2025-02-16 15:20  forgotmyhandle  阅读(44)  评论(0)    收藏  举报