In mathematics you don't understand things, you just get used to them.

标签 data structures 下的文章

link。

还算萌,但是代码有些难写……

你首先会想要

int n, m, fa[20][300100], pa[300100], dep[300100], cnt[900100];
int ldf[300100], rdf[300100], dfc, qwq;
vi<int> G[300100];
struct path {
    int x, y, sx, sy, lca;
};
void dfs(int x, int pre) {
    fa[0][x] = pa[x] = pre;
    dep[x] = dep[pre]+1;
    ldf[x] = ++dfc;
    for (int i=1; i<20; ++i) {
        fa[i][x] = fa[i-1][fa[i-1][x]];
    }
    for (auto y : G[x]) {
        if (y != pre) {
            dfs(y, x);
        }
    }
    rdf[x] = dfc;
}
int lca(int x, int y) {
    if (dep[x] < dep[y]) {
        swap(x, y);
    }
    for (int i=19; i>=0; --i) {
        if (dep[fa[i][x]] >= dep[y]) {
            x = fa[i][x];
        }
    }
    if (x == y) {
        return x;
    }
    for (int i=19; i>=0; --i) {
        if (fa[i][x] != fa[i][y]) {
            x = fa[i][x], y = fa[i][y];
        }
    }
    return pa[x];
}
int jump(int x, int d) {
    if (d < 0) {
        return n+(++qwq);
    }
    for (int i=19; i>=0; --i) {
        if ((d>>i)&1) {
            x = fa[i][x];
        }
    }
    return x;
}
int bit[300100];
void add(int x, int y) {
    for (; x<=n; x+=x&-x) {
        bit[x] += y;
    }
}
int qry(int x) {
    int res = 0;
    for (; x; x-=x&-x) {
        res += bit[x];
    }
    return res;
}
int qry(int l, int r) {
    return qry(r)-qry(l-1);
}
void executer() {
    cin >> n;
    for (int i=1,x,y; i<n; ++i) {
        cin >> x >> y;
        G[x].push_back(y);
        G[y].push_back(x);
    }
    dfs(1, 0);
    cin >> m;
    vi<path> paths(m);
    for (auto& it : paths) {
        cin >> it.x >> it.y;
        it.lca = lca(it.x, it.y);
        it.sx = jump(it.x, dep[it.x]-dep[it.lca]-1);
        it.sy = jump(it.y, dep[it.y]-dep[it.lca]-1);
        if (it.sx > it.sy) {
            swap(it.sx, it.sy), swap(it.x, it.y);
        }
    }
    sort(paths.begin(), paths.end(), [&](const path& lhs, const path& rhs) {
        return dep[lhs.lca] < dep[rhs.lca]
               || (dep[lhs.lca] == dep[rhs.lca] && lhs.lca < rhs.lca)
               || (lhs.lca == rhs.lca && lhs.sx > rhs.sx)
               || (lhs.sx == rhs.sx && lhs.sy > rhs.sy);
    });
    LL ans = 0;
    for (int i=0; i<m;) {
        int j = i;
        while (j < m-1 && paths[j+1].lca == paths[j].lca) {
            j++;
        }
        LL now = 0;
        for (int l=i; l<=j;) {
            int r = l;
            while (r < j && paths[r+1].sx == paths[r].sx) {
                r++;
            }
            for (int k=l; k<=r; ++k) {
                ans += now-cnt[paths[k].sy];
            }
            for (int k=l; k<=r; ++k) {
                cnt[paths[k].sx]++, cnt[paths[k].sy]++;
            }
            now += r-l+1, l = r+1;
        }
        for (int k=i; k<=j; ++k) {
            cnt[paths[k].sx] = cnt[paths[k].sy] = 0;
        }
        for (int k=i; k<=j; ++k) {
            ans += qry(ldf[paths[k].lca], rdf[paths[k].lca]);
            if (paths[k].sx <= n) {
                ans -= qry(ldf[paths[k].sx], rdf[paths[k].sx]);
            }
            if (paths[k].sy <= n) {
                ans -= qry(ldf[paths[k].sy], rdf[paths[k].sy]);
            }
        }
        for (int k=i; k<=j; ++k) {
            add(ldf[paths[k].x], 1), add(ldf[paths[k].y], 1);
        }
        i = j+1;
    }
    cout << ans << "\n";
}

讲课讲得非常清楚啊,我绝赞膜拜。节奏可以,思路清晰,解法自然,为讲师点赞。

第一个题是 loj3282 / joisc2020 - Treatment Project。原问题由 $\left(S, t, w\right)$ 三个维度构成,分别表示村民被治疗的状态,时间,花费。比较自然的思路是针对 $t$ 做规划,但是这样有一个问题——$t$ 和 $S$ 是息息相关的,若从 $t$ 入手则几乎不得不记录 $S$,这是不被接受的。考虑直接 $S$ 入手。如果我可以考虑一段连续的区间而不是子序列,是不是复杂度一定会降?那么考察前缀,具体,设 $f_i$ 表示将 $[1, i]$ 的村民治好的最小花费。进一步地,不妨将 $f_i$ 重新描述为将 $[1, r_i]$ 的村民治好的最小花费。

为什么这样设计状态就不需要考虑时间的影响了(不是说整体,仅仅是在设计状态的时候)?我个人的理解是这样做将村民,或者说 $S$,作为第一个研究的对象,而时间则作为后续求解中对 $f$ 的限制,所以放到后面考虑即可。

转移方程即为 $\displaystyle f_i\gets\min_{1\leqslant j<i,r_j-l_i+1\geqslant|t_i-t_j|}\{f_j\}+w_i$。将绝对值拆开,这里以 $t_i\geqslant t_j$ 为例。那么转移的条件就成了 $r_j+t_j+1\geqslant t_i+l_i$。那么现在转移有二维偏序的关系,即 $t_i\geqslant t_j,r_j+t_j+1\geqslant t_i+l_i$。在优化之前,首先注意到这是一个最短路模型,转移条件即连边的条件,考虑用最优的 $f_j$ 去松弛 $f_i$,那么每个 $f_i$ 就只会被松弛一次(几乎是自明的),于是按 $t$ 排序后用线段树维护即可。

#include <bits/stdc++.h>
using namespace std;
// implementation: time-ordered
using ll = long long;
using pli = pair<ll, int>;
const ll inf = 1e18;
int n, m;
ll dp[100100];
struct node {
    int l, r, t;
    ll w;
} a[100100];
priority_queue<pli, vector<pli>, greater<pli>> q; // slacked
struct segment_tree {
    ll mix[400100], miy[400100];
    void pull(int now) {
        mix[now] = min(mix[now*2], mix[now*2+1]);
        miy[now] = min(miy[now*2], miy[now*2+1]);
    }
    void ins(int pos, ll x, ll y, int now=1, int l=1, int r=n) {
        if (l == r) {
            mix[now] = x, miy[now] = y;
            return;
        }
        int mid = (l+r)/2;
        if (mid >= pos) ins(pos, x, y, now*2, l, mid);
        else ins(pos, x, y, now*2+1, mid+1, r);
        pull(now);
    }
    void slackx(int lq, int rq, ll lim, ll dlt, int now=1, int l=1, int r=n) {
        if (lq > rq || l > rq || r < lq || mix[now] > lim) return;
        if (l == r) {
            dp[l] = dlt+a[l].w;
            q.emplace(dp[l], l);
            mix[now] = miy[now] = inf;
            return;
        }
        int mid = (l+r)/2;
        slackx(lq, rq, lim, dlt, now*2, l, mid);
        slackx(lq, rq, lim, dlt, now*2+1, mid+1, r);
        pull(now);
    }
    void slacky(int lq, int rq, ll lim, ll dlt, int now=1, int l=1, int r=n) {
        if (lq > rq || l > rq || r < lq || miy[now] > lim) return;
        if (l == r) {
            dp[l] = dlt+a[l].w;
            q.emplace(dp[l], l);
            mix[now] = miy[now] = inf;
            return;
        }
        int mid = (l+r)/2;
        slacky(lq, rq, lim, dlt, now*2, l, mid);
        slacky(lq, rq, lim, dlt, now*2+1, mid+1, r);
        pull(now);
    }
} sgt;
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> m >> n;
    for (int i=1;i<=n;++i) {
        dp[i] = inf;
        cin >> a[i].t >> a[i].l >> a[i].r >> a[i].w;
    }
    sort(a+1, a+n+1, [&](node a, node b) {
        return a.t < b.t;
    });
    for (int i=1;i<=n;++i) {
        if (a[i].l == 1) {
            dp[i] = a[i].w;
            q.emplace(dp[i], i);
            sgt.ins(i, inf, inf);
        }
        else {
            sgt.ins(i, a[i].l-a[i].t, a[i].l+a[i].t);
        }
    }
    while (!q.empty()) {
        // use nodes slacked to slack other nodes
        int x = q.top().second;
        q.pop();
        sgt.slackx(1, x-1, a[x].r-a[x].t+1, dp[x]);
        sgt.slacky(x+1, n, a[x].r+a[x].t+1, dp[x]);
    }
    ll ans = inf;
    for (int i=1;i<=n;++i) {
        if (a[i].r == m) {
            ans = min(ans, dp[i]);
        }
    }
    if (ans == inf) cout << "-1\n";
    else cout << ans << "\n";
    return 0;
}

第二个题是 codeforces - 1476F整个课上唯一一个自己想出来的题,还错了个细节。直接 dp 有着自明的后效性,说两种消除之的方法。第一个是「将 $i$ 点亮灯的能力挂到 $i$ 能够点亮的最右边的灯上」,这样一次操作只会影响前面的,这消除了后效性。第二个直接在 dp 状态上下手,也是正解的思路,即设 $f_i$ 表示以 $[1, i]$ 的灯能够点亮的最长前缀(完全可能超过 / 不足 $i$),这样相当于将「点灯的灯」和「被点的灯」隔离开了,影响就被消除了。

因为我写的第二个,所以说一下第二个(其实是差不多的)。先考虑 $i$ 向右照的情况,转移非常简单 $f_i\gets\max\{f_i, i+p_i\}$(当然 $f_i$ 要先继承 $f_{i-1}$,这也昭示了其单调性)。$i$ 向左照的情况比较复杂。考虑有这样一个 $j$,满足 $f_j\geqslant i-p_i-1$(保证维护的是前缀),那么让 $\forall t,s.t.j<t<i$ 向右照是最优的,贪心地想,我们就需要使得 $j$ 最小,由于 $f$ 有单调性,直接二分即可,然后 RMQ 找 $(j, i)$ 区间中最大的 $t+p_t$ 即可。

#include <bits/stdc++.h>
using namespace std;
int n, p[300100], dp[300100], dp2[20][300100], lgs[300100], pre[300100], isl[300100];
int get(int l, int r) {
    if (l > r) return 0;
    int k = lgs[r-l+1];
    return max(dp2[k][l], dp2[k][r-(1<<k)+1]);
}
void print(int now) {
    if (now == 0) return;
    print(pre[now]);
    if (isl[now]) {
        cout << "R";
        return;
    }
    for (int i=pre[now]+1; i<now; ++i) cout << "R";
    cout << "L";
}
void solve() {
    cin >> n;
    for (int i=1;i<=n;++i) {
        cin >> p[i];
        dp2[0][i] = i+p[i];
    }
    for (int i=1;(1<<i)<=n;++i) {
        for (int j=1;j+(1<<i)-1<=n;++j) {
            dp2[i][j] = max(dp2[i-1][j], dp2[i-1][j+(1<<(i-1))]);
        }
    }
    pre[1] = 0, isl[1] = 1;
    for (int i=2;i<=n;++i) {
        dp[i] = dp[i-1], pre[i] = i-1, isl[i] = 1;
        if (dp[i-1] >= i) {
            dp[i] = max(dp[i], i+p[i]);
        }
        int l = 0, r = i-1, j = -1, mid;
        while (l <= r) {
            mid = (l+r)/2;
            if (dp[mid] >= i-p[i]-1) r = mid-1, j = mid;
            else l = mid+1;
        }
        if (j == -1) continue;
        if (max(i-1, get(j+1, i-1)) >= dp[i]) {
            isl[i] = 0, pre[i] = j;
        }
        dp[i] = max({dp[i], i-1, get(j+1, i-1)});
    }
    if (dp[n] >= n) {
        cout << "YES\n";
        print(n);
        cout << "\n";
        return;
    }
    cout << "NO\n";
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    int tt;
    for (int i=2; i<300100; ++i) {
        lgs[i] = lgs[i>>1]+1;
    }
    for (cin>>tt;tt--;) {
        solve();
    }
}

第三个题是 codeforces - 1523F。再次考虑这个题的维度 $(S, t, i, w)$,分别表示点亮传送塔的集合,时间,所处位置(传送塔或任务点),完成任务数量。据此可以写出一个朴素的 dp,$f_{S, i, w}$ 表示点亮传送塔的集合为 $S$,当前在位置 $i$,完成了 $w$ 个任务,所花费的最小时间。这里把题目中的「答案」完成任务数量作为状态并非不自然,是单纯因为时间太大忍不下。这里有两个重要的观察:

  • 处在传送塔时,不关心自己的位置;
  • 处在任务点时,不关心当前的时间。

正确性不必多言,关键在于观察到之的洞察力。于是我们可以优化状态(这里的根据是,传送塔 $\cup$ 任务点 $=$ 所有地址),分别定义 $f_{S, i}$,$g_{S, i}$ 表示点亮传送塔的集合为 $S$,完成了 $i$ 个任务,并且现在处于传送塔的最小时间,和点亮传送塔的集合为 $S$,在 $i$ 个地址,最大完成任务总数。转移分 传送塔 $\rightarrow$ 任务点、传送塔 $\rightarrow$ 传送塔、任务点 $\rightarrow$ 任务点、任务点 $\rightarrow$ 传送塔 讨论即可。同时还有转移顺序的问题,具体见代码。

#include <bits/stdc++.h>
#define cmin(x, y) x = min(x, y)
#define cmax(x, y) x = max(x, y)
using namespace std;
const int inf = 0x3f3f3f3f;
int n, m, ans = -inf;
int w[16484][120], up, f[16484][120], g[16484][120];
struct pnt {
    int x, y, t;
} a[120];
int dst(int i, int j) {
    return abs(a[i].x-a[j].x)+abs(a[i].y-a[j].y);
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> n >> m;
    up = 1<<n;
    for (int i=0; i<n; ++i) {
        cin >> a[i].x >> a[i].y;
    }
    for (int i=n; i<n+m; ++i) {
        cin >> a[i].x >> a[i].y >> a[i].t;
    }
    sort(a+n, a+n+m, [&](pnt x, pnt y) {
        return x.t < y.t;
    });
    for (int s=0; s<up; ++s) {
        for (int i=0; i<n+m; ++i) {
            w[s][i] = inf;
            for (int j=0; j<n; ++j) {
                if (s&(1<<j)) cmin(w[s][i], dst(i, j));
            }
        }
    }
    for (int s=0; s<up; ++s) {
        for (int i=0; i<m; ++i) f[s][i] = inf, g[s][i] = -inf;
        f[s][m] = inf;
    }
    for (int i=0; i<n; ++i) f[1<<i][0] = 0;
    for (int i=0; i<m; ++i) g[0][i] = 1;
    for (int s=0; s<up; ++s) {
        for (int i=0; i<=m; ++i) {
            if (f[s][i] != inf) {
                for (int j=0; j<n; ++j) {
                    if ((s&(1<<j)) == 0) {
                        cmin(f[s|(1<<j)][i], f[s][i]+w[s][j]);
                    }
                }
                for (int j=0; j<m; ++j) {
                    if (f[s][i]+w[s][j+n] <= a[j+n].t) {
                        cmax(g[s][j], i+1);
                    }
                }
            }
        }
        for (int i=0; i<m; ++i) {
            if (g[s][i] >= 0) {
                for (int j=0; j<n; ++j) {
                    if ((s&(1<<j)) == 0) {
                        cmin(f[s|(1<<j)][g[s][i]], min(dst(i+n, j), w[s][j])+a[i+n].t);
                    }
                }
                for (int j=i+1; j<m; ++j) {
                    if (min(dst(i+n, j+n), w[s][j+n])+a[i+n].t <= a[j+n].t) {
                        cmax(g[s][j], g[s][i]+1);
                    }
                }
                cmax(ans, g[s][i]);
            }
        }
    }
    cout << ans << "\n";
    return 0;
}

link。

有点狗,但还算个好题。

设定 $f_i(x)=a_ix-x^3$,$\Delta_i(x)=f_i(x)-f_i(x-1)$,可以洞察到 $\Delta_i(x)$ 在正自然数中是递减的。那么我们就可以贪心了。贪心时我们维护一个向量 $(b_1,\dots,b_n)$,分别表示 $\Delta_i(b_i)$,初始全为零。放进优先队列里面,每次取一个出来(记为 $\textit{id}$)将 $b_{\textit{id}}$ 增量 $1$,再放入优先队列里面。最终我们得到的即是使得答案最优的向量。

复杂度不可接受,我们优化的方向应当是提高生产力,怎样批量决定该做哪些。考虑二分一个标准线 $t$,如果存在向量满足 $\sum b_i\leqslant k$,且我们只做了 $\Delta_i(b_i)\geqslant t$ 的函数,就行了。设 $f(t)$ 为达到标准线的函数个数,最后我们得到的 $t$ 满足 $f(t)\leqslant k<f(t+1)$。

然后通过调整可得到答案。

#include <bits/stdc++.h>
using namespace std;
using ll = __int128;
#define int ll
int n, k, a[100100], b[100100];
ll of(ll x, ll a) {
    return a*x-x*x*x;
}
ll df(ll x, ll i) {
    return of(x, a[i])-of(x-1, a[i]);
}
int f(int i, int stl) {
    int l = 0, r = a[i], mid, res = 0;
    while (l <= r) {
        mid = (l+r)/2;
        if (df(mid, i) >= stl) l = mid+1, res = mid;
        else r = mid-1;
    }
    return res;
}
bool check(ll cur) {
    // @cur stands for the standard line
    ll sm = 0;
    for (int i=1;i<=n;++i) {
        int ret = f(i, cur);
        sm += ret, b[i] = ret;
    }
    return sm <= k;
}
ll bsrh(ll l, ll r) {
    ll mid, res = -1;
    while (l <= r) {
        if(check(mid = (l+r)/2)) r = mid-1, res = mid;
        else l = mid+1;
    }
    return res;
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    long long tmp;
    cin >> tmp;
    n = tmp;
    cin >> tmp;
    k = tmp;
    for (int i=1;i<=n;++i) {
        cin >> tmp;
        a[i] = tmp;
    }
    int t = bsrh(-9e18, 9e18), sum = 0;
    check(t-1);
    for (int i=1;i<=n;++i) sum += b[i];
    for (int i=1;i<=n;++i) {
        int adj = f(i, t-1)-f(i, t);
        if (sum > k) {
            if (sum-adj <= k) {
                b[i] -= (sum-k);
                break;
            }
            else {
                sum -= adj, b[i] -= adj;
            }
        }
    }
    for (int i=1;i<=n;++i) {
        tmp = b[i];
        cout << tmp << " \n"[i == n];
    }
    return 0;
}

link.

感觉好久没写过题解了, 这就是永远在骚动的得不到吧.

星尘 infinity 真的非常行, 就算是 ja voicebase 都不知道吊打那群日 v 多少圈. 我推荐你们都去听一听.

chin voicebase 更是重量级, 乱杀 vs 那堆. 不得不说个体技术力质的进步才是社会发展的关键, 什么大家进步了总体就进步了就是扯淡.

当然也不能像赫鲁晓夫那群天才一样鼓吹唯生产力论, 但是用 $+\infty$ 的 p 们确实交出了优秀的答卷, 时至今日的格局只能说是原本基础决定的.


首先如果没有删除操作的话, 这题可以 parallel search 随便做做了. 加上删除操作再套个 segment tree beats 就行.


题解大概就是这样, 我主要来说下这个 segment tree beats. 首先这里没有必要真的写一个出来, 因为注意到是单点询问. 但是这里的线段树有一些不同于传统的线段树, 尽管传统线段树也是一棵 leafy tree, 但是这里树的非叶结点上是没有信息需要维护的. 可以把 lazy propagation 和你维护的幺半群放到一个数组来写.

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

const i64 inf = 1e9;
int N, M, Q, sz, h, ans[250100];
i64 dat[250100];
struct rec {
  i64 p, q;
} lz[524388];
struct req {
  i64 a, b, c, d;
} q[250100];
void add(int x, i64 v) {
  for (; x <= N + 1; x += x & -x) dat[x] += v;
}
void add(int l, int r, i64 v) { add(l, v), add(r + 1, -v); }
i64 sum(i64 x) {
  i64 res = 0;
  for (; x; x -= x & -x) res += dat[x];
  return res;
}
rec composition(rec f, rec v) { return {v.p + f.p, max(v.q + f.p, f.q)}; }
void propagate(int x, rec f) { lz[x] = composition(f, lz[x]); }
void push(int x) {
  propagate(x * 2, lz[x]), propagate(x * 2 + 1, lz[x]), lz[x] = {0, -inf};
}
void add(int l, int r, rec f) {
  assert(0 <= l && l <= r && r <= N);
  if (l == r) return;
  l += sz, r += sz;
  for (int i = h; i >= 1; --i) {
    if (((l >> i) << i) != l) push(l >> i);
    if (((r >> i) << i) != r) push((r - 1) >> i);
  }
  for (; l < r; l >>= 1, r >>= 1) {
    if (l & 1) propagate(l++, f);
    if (r & 1) propagate(--r, f);
  }
}
i64 get(i64 x) {
  assert(0 <= x && x < N);
  for (i64 i = h; i >= 1; --i) push((x + sz) >> i);
  return max(lz[x + sz].p, lz[x + sz].q);
}

void dac(int l, int r, const vector<int>& id) {
  if (id.empty()) return;
  if (l == r) {
    for (auto&& it : id) ans[it] = l;
    return;
  }
  int mid = (l + r) / 2;
  for (int i = l; i <= mid; ++i)
    if (q[i].c && q[i].d) add(q[i].a, q[i].b, q[i].d);
  vector<int> ql, qr;
  for (auto&& it : id) (sum(q[it].a) >= q[it].b ? ql : qr).emplace_back(it);
  dac(mid + 1, r, qr);
  for (int i = l; i <= mid; ++i)
    if (q[i].c && q[i].d) add(q[i].a, q[i].b, -q[i].d);
  dac(l, mid, ql);
}

signed main() {
  ios::sync_with_stdio(0), cin.tie(0);
  cin >> N >> M >> Q;
  h = ceil(log2(N)), sz = 1 << h;
  for (int i = 0; i <= N; ++i) lz[i + sz] = {0, -inf};
  vector<int> is;
  for (i64 o, l, r, c, k, i = 1; i <= Q; ++i) {
    cin >> o >> l >> r, c = k = 0;
    if (o == 1)
      cin >> c >> k, add(l, r, k), add(l - 1, r, {k, 0});
    else if (o == 2)
      cin >> k, add(l - 1, r, {-k, 0});
    else
      r += sum(l) - get(l - 1), is.emplace_back(i);
    q[i] = {l, r, c, k};
  }
  memset(dat, 0, sizeof(dat)), dac(1, Q + 1, is);
  for (int i = 1; i <= Q; ++i)
    if (q[i].c == 0 && q[i].d == 0)
      cout << q[ans[i] > i ? 0 : ans[i]].c << "\n";
  return 0;
}

link。

首先所有的 activated nodes 组合成了一棵以 $1$ 为根的有根树。询问即求由 activated nodes 组成的树的最大匹配。对于树上最大匹配有一个贪心策略:自底向上匹配当前点和其父亲,删除这两个点,直至只剩一个点或空树。若为空树,则树存在完美匹配。

Claim: 对于树 $\textbf{T}=(\textbf{V},\textbf{E})$,若存在完美匹配,当且仅当 $\displaystyle\left(\sum_{u\in\textbf{V}}[|\text{subtree}(u)|\bmod2=1]\right)=\left(\sum_{u\in\textbf{V}}[|\text{subtree}(u)|\bmod2=0]\right)$

Proof: 两个简单的观察即可证明:(1)每个子树大小为偶数的结点有且仅有一个子树大小为奇数的后继;(2)每个子树大小为奇数的结点的父亲子树大小为偶数。

所以偶数奇数两两对应,以上论断的充分性得证。其必要性的正确性比较平凡,故略。

然后我们需要支持的操作就只有加入一个叶子结点,反转一条无拐点的链上结点的标记。整棵树的形态是固定的,HLD 维护即可。具体方案的询问次数不超过 10 次,朴素 $O(n)$ 寻找即可。

然而翻转链部分暴力也能过而且和线段树没啥本质区别……

#pragma GCC optimize("Ofast,unroll-loops")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,avx2,tune=native")
#include<bits/stdc++.h>
#define cmin(x, y) x = std::min(x, y)
#define cmax(x, y) x = std::max(x, y)
#define fors(i, l, r, ...) for(int i = (l), REP##i = (r), ##__VA_ARGS__; i <= REP##i; ++i)
#define dfors(i, r, l, ...) for(int i = (r), REP##i = (l), ##__VA_ARGS__; i >= REP##i; --i)
int n, up[200100], all, on[200100], cnt, sz[200100], son[200100], top[200100], fa[200100], dfn[200100];
// params: @up[i]: identity of edge (i, fa[i]); @on[i]: is rev[i] activated; @all: amout of nodes activated;
//      @cnt: amout of odd nodes
std::vector<std::pair<int, int>> adj[200100];
std::set<int> S;
long long ans;
namespace hld {
    int tt;
    void dfs_sz(const int x, const int fa) {
        sz[x] = 1, ::fa[x] = fa;
        for(const auto [y, id] : adj[x]) if(y != fa) {
            dfs_sz(y, x);
            if(sz[y] > sz[son[x]]) son[x] = y;
        }
    }
    void dfs_hld(const int x, const int tp) {
        top[x] = tp, dfn[x] = ++tt;
        if(son[x]) dfs_hld(son[x], tp);
        for(const auto [y, id] : adj[x]) {
            if(y == fa[x]) up[dfn[x]] = id;
            if(y != fa[x] && y != son[x]) dfs_hld(y, y);
        }
    }
    void init() { dfs_sz(1, 0), dfs_hld(1, 1); }
}
signed main() {
    std::ios::sync_with_stdio(0);
    std::cin >> n;
    fors(i, 1, n-1, x, y) {
        std::cin >> x >> y;
        adj[x].emplace_back(y, i);
        adj[y].emplace_back(x, i);
    }
    on[1] = all = cnt = 1, hld::init();
    for(int op, x; "eternal love"; std::cout << "\n") {
        if(std::cin >> op, S.clear(); op == 1) {
            for(std::cin >> x, all++; x; x = fa[top[x]])
                fors(i, dfn[top[x]], dfn[x]) cnt += (on[i]?-1:1),ans += (on[i]?-1:1)*up[i],on[i] ^= 1;
            std::cout << ((all == cnt*2)?ans:0);
        } else if(op == 2) {
            if(all != cnt*2) std::cout << "0";
            else {
                fors(i, 2, n) if(on[i]) S.emplace(up[i]);
                std::cout << cnt;
                for(const int x : S) std::cout << " " << x;
            }
        } else break;
    }
    return 0;
}