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;
}

浙公网安备 33010602011771号