20241028

T1

P11238 铁路 2

考虑最终路线一定是从起点走到直径的一个端点,然后再走到直径的另一个端点(也可以不走),然后走到终点,设直径端点分别为 \(p, q\),则 \(joy(s, t) = \min \{ \max\{ dist(s, p), dist(s, q) \}, \max\{ dist(p, t). dist(q, t) \} \}\)。然后求出每个点到两个端点距离的最大值就可以直接算贡献了。

代码
#include "railroad2.h"
#include <iostream>
#include <algorithm>
#include <string.h>
#include <vector>
#define int long long
using namespace std;
const int P = 1000000007;
int n;
int head[500005], nxt[1000005], to[1000005], ew[1000005], ecnt;
void add(int u, int v, int ww) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt, ew[ecnt] = ww; }
int mxd, X;
int dist[500005];
void dfs(int x, int fa, int d) {
    dist[x] = max(dist[x], d);
    if (d > mxd) 
        X = x, mxd = d;
    for (int i = head[x]; i; i = nxt[i]) {
        int v = to[i];
        if (v != fa) 
            dfs(v, x, d + ew[i]);
    }
}
int ans;
signed travel(vector<signed> U, vector<signed> V, vector<signed> W) {
    n = U.size() + 1;
    for (int i = 0; i < n - 1; i++) {
        int u = U[i], v = V[i], ww = W[i];
        ++u, ++v;
        add(u, v, ww);
        add(v, u, ww);
    }
    dfs(1, 0, 0);
    memset(dist, 0, sizeof dist);
    int p = X;
    mxd = 0, X = 0;
    dfs(p, 0, 0);
    dfs(X, 0, 0);
    sort(dist + 1, dist + n + 1);
    for (int i = 1; i <= n; i++) ans += dist[i] % P * (n - i) % P;
    return ans * 2 % P;
}

T2

P11237 警察与小偷

不难发现小偷的行动必然是向警察进行一段走,然后走向以警察为根时他的子树。设小偷一开始在 \(x\),警察一开始在 \(y\),再设中间的转折点为 \(p\),则不会创上警察的 \(p\) 的范围一定是 \(x \rightarrow y\) 的一段前缀。

然后比如说现在有 \(3\) 个点的一段路 \(x -- y --- d\),其中 \(x\) 是警察的出发位置,\(y\) 是小偷的出发位置,\(d\) 是这段路的终点,这段路的长度我们也记作 \(d\)。设 \(x\)\(y\) 中间的距离为 \(a\)\(y\)\(d\) 之间的距离为 \(b\),则若 \(v_x > v_y\),警察抓住小偷的时刻即为 \(\min \{ \frac{a}{v_x - v_y}, \frac{d}{v_x} \}\)。然后推一些式子会发现当 \(\frac{b}{a + b} < \frac{v_y}{v_x}\) 时这个 \(\min\) 取到后者。

然后考虑原问题。容易发现当转折点确定时小偷一定跑到以警察为根时他子树内最深的叶子,则这就变成上面说的一段路的问题。在小偷走到转折点时考虑,则会发现 \(a\) 随着小偷向警察走而单减,\(b\) 随着小偷向警察走而单增。稍微分析一下会发现此时 \(\frac{b}{a + b}\) 是单增的。也就是说在 \(x \rightarrow y\) 的路径上,取 \(\min\) 的后者的是一段前缀,而取前者的是所有 \(p\) 的合法位置的后缀。因此可以找到分界点然后计算。

接下来再考虑当小偷向警察走的时候,取后者的(最终)答案必然是单增,而取前者的(最终)答案必然是单减。因此我们肯定希望两边取的点都尽量接近分界点,于是取分界点两边分别计算答案即可。

关于取前者的答案单减:这等价于两人同时开始跑,然后小偷先向警察跑一段,然后才掉头。很显然如果要让追及时间尽量长的话没有人会蠢到先向追着自己的人冲一段。

我写的是双 log。倍增太慢了,容易被卡常,建议全都用树剖。

代码
#include "police.h"
#include <iostream>
#include <cassert>
#include <vector>
#include <array>
#define int long long
using namespace std;
__int128 gcd(__int128 a, __int128 b) { return b ? gcd(b, a % b) : a; }
struct Fraction {
    __int128 a, b;
    inline void operator()() {
        __int128 x = gcd(a, b);
        a /= x, b /= x;
    }
};
Fraction _max(Fraction a, Fraction b) {
    a(), b();
    __int128 x = a.b * b.b / gcd(a.b, b.b);
    return (a.a * (x / a.b) > b.a * (x / b.b)) ? a : b;
}
int n, q;
int head[100005], nxt[200005], to[200005], ew[200005], ecnt;
void add(int u, int v, int ww) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt, ew[ecnt] = ww; }
int f[100005], ff[100005], g[100005];
int dep[100005], wdep[100005];
int fa[100005], dfn[100005], R[100005], *L = dfn, ncnt;
int top[100005], sz[100005], son[100005];
int _dfn[100005];
void dfs1(int x, int fa) {
    ::fa[x] = fa;
    dep[x] = dep[fa] + 1;
    sz[x] = 1;
    for (int i = head[x]; i; i = nxt[i]) {
        int v = to[i];
        if (v != fa) {
            wdep[v] = wdep[x] + ew[i];
            dfs1(v, x);
            sz[x] += sz[v];
            (sz[v] > sz[son[x]]) ? (son[x] = v) : 0;
            (f[v] + ew[i] >= f[x]) ? (g[x] = f[x], f[x] = f[v] + ew[i], ff[x] = v) : (g[x] = max(g[x], f[v] + ew[i]));
        }
    }
}
void dfs2(int x, int fa, int y) {
    (y >= f[x]) ? (g[x] = f[x], f[x] = y, ff[x] = fa) : (g[x] = max(g[x], y));
    for (int i = head[x]; i; i = nxt[i]) {
        int v = to[i];
        if (v != fa) 
            dfs2(v, x, ew[i] + (ff[x] == v ? g[x] : f[x]));
    }
}
void dfs3(int x, int t) {
    _dfn[dfn[x] = ++ncnt] = x, top[x] = t;
	son[x] ? dfs3(son[x], t) : void();
	for (int i = head[x]; i; i = nxt[i]) {
		int v = to[i];
		if (v != fa[x] && v != son[x]) 
			dfs3(v, v);
	}
}
inline int Kanc(int x, int k) {
	while (dep[x] - dep[top[x]] < k) k -= (dep[x] - dep[top[x]] + 1), x = fa[top[x]];
	return _dfn[dfn[x] - k];
}
inline 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);
}
inline int dist(int x, int y) { return wdep[x] + wdep[y] - wdep[LCA(x, y)] * 2; }
int Kth(int a, int b, int k) {
    int x = LCA(a, b);
    if (dep[a] - dep[x] >= k) 
        return Kanc(a, k);
    else 
        return Kanc(b, dep[a] + dep[b] - dep[x] * 2 - k);
}
inline int F(int x, int y) { return ff[x] == y ? g[x] : f[x]; }
int FF(int x, int y) { return (L[x] <= dfn[y] && dfn[y] <= R[x]) ? F(x, Kanc(y, dep[y] - dep[x] - 1)) : F(x, fa[x]); }
int A;
pair<int, int> get1(int x, int y, int vx, int vy) {
    int wd = wdep[x] + wdep[y] - wdep[A] * 2;
    int lim = (wd * vx + vx + vy - 1) / (vx + vy);
    int d = dep[x] + dep[y] - dep[A] * 2;
    int l = 0, r = d, mid, ans = x, tmp, ans2 = 0;
    while (l <= r) {
        mid = (l + r) >> 1;
        if (dist(x, tmp = Kth(x, y, mid)) < lim) 
            ans2 = mid, ans = tmp, l = mid + 1;
        else 
            r = mid - 1;
    }
    return make_pair(ans, ans2);
}
pair<int, int> get2(int x, int y, int vx, int vy, int u) {
    int l = 0, r = u, mid, ans = 0, ans2 = -1;
    int du = wdep[x] + wdep[y] - wdep[A] * 2, dx;
    while (l <= r) {
        mid = (l + r) >> 1;
        int t = Kth(x, y, mid);
        dx = dist(t, x);
        int b = FF(t, y);
        if ((__int128)b * vx * vy < vx * ((__int128)b * vx + (__int128)du * vx - (__int128)dx * (vx + vy))) 
            ans2 = mid, ans = t, l = mid + 1;
        else 
            r = mid - 1;
    }
    return make_pair(ans, ans2);
}
Fraction calcd(int x, int y, int vp) {
    int d = dist(x, y);
    if (L[x] <= dfn[y] && dfn[y] <= R[x]) 
        return (Fraction) { d + F(x, Kanc(y, dep[y] - dep[x] - 1)), vp };
    else 
        return (Fraction) { d + F(x, fa[x]), vp };
}
Fraction calca(int p, int x, int y, int vx, int vy) {
    int dx = dist(p, x), du = wdep[x] + wdep[y] - 2 * wdep[A];
    return (Fraction) { (__int128)du * vx - 2 * dx * vx, vx * (vy - vx) };
}
vector<array<int, 2>> police_thief(vector<signed> A, vector<signed> B, vector<signed> D, vector<signed> P, vector<signed> V1, vector<signed> T, vector<signed> V2) {
    *dep = -1;
    n = A.size() + 1;
    q = P.size();
    for (int i = 0; i < n - 1; i++) {
        int u = A[i] + 1, v = B[i] + 1, ww = D[i];
        add(u, v, ww);
        add(v, u, ww);
    }
    dfs1(1, 0);
    dfs2(1, 0, 0);
    dfs3(1, 1);
    for (int x = 1; x <= n; x++) R[x] = L[x] + sz[x] - 1;
    vector<array<int, 2> > ret;
    for (int _ = 0; _ < q; _++) {
        int Pp = P[_] + 1, Tp = T[_] + 1, vx = V1[_], vy = V2[_];
        ::A = LCA(Pp, Tp);
        pair<int, int> p = get1(Tp, Pp, vy, vx);
        pair<int, int> q = get2(Tp, Pp, vy, vx, p.second);
        Fraction asdf;
        asdf.a = 0, asdf.b = 1;
        if (q.first != 0) 
        	asdf = _max(asdf, calcd(q.first, Pp, vx));
        if (q.first != p.first) 
            asdf = _max(asdf, calca(q.first ? Kth(q.first, Pp, 1) : Tp, Tp, Pp, vy, vx));
        asdf();
        ret.emplace_back((array<int, 2>) { asdf.a, asdf.b });
    }
    return ret;
}

T3

P11236 水果游戏

首先发现两个峰之间夹着的谷是没用的,于是直接干掉。注意到值域只有 \(10\),又是单修区查,考虑线段树维护。发现一旦消的时候有一个同色连续段消剩下奇数个,则这一段左右就无法合并,也就是他们是独立的,可以用分隔符隔开。按照这个规则不断消下去,最终会获得以分隔符为断点的若干单峰序列。注意到在线段树上合并区间的过程中,一个单峰序列如果被分隔符夹在中间,那这个序列不会再与外界有任何接触,因此只需要记下它的答案然后把它扔掉即可。这样每个区间维护的就是以分隔符(如果有的话)为峰的一个单峰序列。然后单峰序列的合并就是把一个序列全都扔进另一个,也就是往一个单峰序列里加东西。当加入的东西出现了谷之后我们就可以把这个分讨来把这个谷干掉。最后询问的时候只需要在区间前后各加一个分隔符把中间夹起来即可保证所有答案都能被统计到。这样的复杂度就是 \(\mathcal{O}(qV\log n)\)

T4

后面忘了。

posted @ 2024-10-28 22:55  forgotmyhandle  阅读(16)  评论(0)    收藏  举报