20241029

forgotmyhandle發表於2024-10-29

T1

16248 島嶼

首先,手模發現任意操作一次即可構造出一組解。於是這題其實是構造題。

發現限制等價於每個三角形中兩種顏色的邊都存在。我們先考慮最外層的一個三角形,也就是一個度數為 \(2\) 的的點所在的三角形。要保證它裡面兩種顏色的邊都存在,最簡單的辦法就是把它的兩個度數染成兩個不同的顏色。然後就等價於沒有這個點,就變成了子問題,可以遞迴下去做了。到最後剩下一個四階完全圖直接特判掉即可。

程式碼
#include "island.h"
#include <iostream>
#include <algorithm>
#include <vector>
#include <random>
#include <array>
#include <queue>
using namespace std;
random_device rd;
mt19937 mtrand(rd());
queue<int> q;
int f[200005];
int deg[200005];
int getf(int x) { return f[x] == x ? x : (f[x] = getf(f[x])); }
int o[200005];
int cnt;
int head[200005], nxt[1000005], to[1000005], ecnt;
void add(int u, int v) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt; }
bool vis[200005];
void construct_two_trees(int N, std::vector<int> U, std::vector<int> V){
    vector<array<int, 2> > t1, t2;
    for (int i = 0; i < N - 3; i++) {
        deg[U[i]]++;
        deg[V[i]]++;
        add(U[i], V[i]);
        add(V[i], U[i]);
    }
    cnt = N;
    for (int i = 0; i < N; i++) deg[i] += 2, add(i, (i + 1) % N), add((i + 1) % N, i);
    for (int i = 0; i < N; i++) {
        if (deg[i] == 2) 
            q.push(i);
    }
    while (cnt > 3) {
        int x = q.front();
        q.pop();
        --cnt;
        vis[x] = 1;
        int cnt = 0;
        for (int i = head[x]; i; i = nxt[i]) {
            int v = to[i];
            if (!vis[v]) {
                --deg[v];
                cnt ? t2.emplace_back((array<int, 2>) { x, v }) : t1.emplace_back((array<int, 2>) { x, v });
                ++cnt;
                if (deg[v] == 2) 
                    q.push(v);
            }
        }
    }
    int a, b, c;
    a = q.front(), q.pop();
    b = q.front(), q.pop();
    c = q.front(), q.pop();
    int x = add_vertex(a, b, c);
    t1.emplace_back((array<int, 2>) { a, x });
    t1.emplace_back((array<int, 2>) { x, c });
    t1.emplace_back((array<int, 2>) { a, b });
    t2.emplace_back((array<int, 2>) { x, b });
    t2.emplace_back((array<int, 2>) { a, c });
    t2.emplace_back((array<int, 2>) { b, c });
    report(t1);
    report(t2);
}

T2

洛谷 P11239 跳躍遊戲

等價於選一堆點,使得相鄰兩個距離不小於 \(K\)。發現選的點要麼正好和上一個相距 \(K\),要麼就是在一個同色連續段的開頭。否則考慮將選的點往前移一個,發現收益不變,落點往前移了一個。而這是不劣的。

樸素 dp,然後考慮把序列每 \(K\) 個分一段,發現每個段內是不會出現互相轉移的。因此依次考慮每一段,維護每個段內的 dp 值。每次往後走一段時,若這個段內沒有連續段的開頭,則直接做全域性加。否則考慮每個連續段開頭,

剩下的你先別急。

T3

洛谷 P11241 病毒

點分治最佳化建圖板子。對每個分治中心子樹內的每個深度建一個點,該深度內的每個點向它連邊(或它向該深度內的每個點連邊),然後每個深度的點向比它深度少 \(1\) 的點連邊。然後做樹上鄰域連邊只需要用點分樹上每個父親對應深度的點連向它即可。

程式碼
#include "virus.h"
#include <iostream>
#include <string.h>
#include <vector>
#include <array>
#include <queue>
#define int long long
using namespace std;
const int inf = 0x3f3f3f3f3f3f3f3f;
struct node {
	int x, dis;
};
bool operator<(node a, node b) { return a.dis > b.dis; }
priority_queue<node> q;
int dist[5000005];
int head[5000005], nxt[10000005], to[10000005], ew[10000005], ecnt;
// void add(int u, int v) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt; }
int dep[1000005], top[1000005], son[1000005], sz[1000005], f[1000005];
void add(int u, int v, int ww = 0) {
	// cout << u << " " << v << "\n";
	to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt, ew[ecnt] = ww;
}
int ncnt;
int c[5000005];
bool vis[5000005];
void dijkstra() {
	while (!q.empty()) {
		node tmp = q.top();
		q.pop();
		int x = tmp.x;
		if (vis[x]) 
			continue;
		vis[x] = 1;
		for (int i = head[x]; i; i = nxt[i]) {
			int v = to[i];
			if (dist[v] > dist[x] + ew[i]) 
				q.push((node) { v, dist[v] = dist[x] + ew[i] });
		}
	}
}
vector<int> G[2000005];
void dfs1(int x, int fa, int d) {
    dep[x] = d;
    f[x] = fa;
    sz[x] = 1;
    for (auto v : G[x]) {
        if (v != fa) {
            dfs1(v, x, d + 1);
            sz[x] += sz[v];
            sz[v] > sz[son[x]] ? (son[x] = v) : 0;
        }
    }
}
void dfs2(int x, int t) {
    top[x] = t;
    son[x] ? dfs2(son[x], t) : void();
    for (auto v : G[x]) {
        v != f[x] && v != son[x] ? dfs2(v, v) : void();
    }
}
inline int LCA(int x, int y) {
    while (top[x] ^ top[y]) (dep[top[x]] < dep[top[y]]) ? (y = f[top[y]]) : (x = f[top[x]]);
    return (dep[x] < dep[y]) ? x : y;
}
inline int Dist(int x, int y) { return dep[x] + dep[y] - 2 * dep[LCA(x, y)]; }
bool mark[1000005];
int rt, all, mnv, mxdist;
vector<int> in[1000005], out[1000005];
void getroot(int x, int fa, int d = 0, int X = 0) {
    mxdist = max(mxdist, d);
    if (X) {
    	add(in[X][d], x);
    	add(x, out[X][d], c[x]);
    }
    sz[x] = 1;
    int msz = 0;
    for (auto v : G[x]) {
        if (v != fa && !mark[v]) {
            getroot(v, x, d + 1, X);
            sz[x] += sz[v];
            msz = max(msz, sz[v]);
        }
    }
    msz = max(msz, all - sz[x]);
    if (msz < mnv) 
        mnv = msz, rt = x;
}
int fa[1000005];
int mxd[1000005];
void dfs3(int x) {
    mxdist = 0;
    getroot(x, 0);
    in[x].resize(mxdist + 1);
    out[x].resize(mxdist + 1);
    mxd[x] = mxdist;
    for (int i = 0; i <= mxdist; i++) in[x][i] = ++ncnt;
    for (int i = 0; i <= mxdist; i++) out[x][i] = ++ncnt;
    for (int i = 0; i < mxdist; i++) add(in[x][i + 1], in[x][i]);
    for (int i = 0; i < mxdist; i++) add(out[x][i], out[x][i + 1]);
    add(in[x][0], x);
	add(x, out[x][0], c[x]);
    mark[x] = 1;
	for (auto v : G[x]) {
        if (!mark[v]) {
            all = sz[v], rt = 0, mnv = inf, mxdist = 0;
            getroot(v, x, 1, x);
            fa[rt] = x;
            dfs3(rt);
        }
    }
}
void Add(int x, int lim, int X) {
    int u = x;
    while (u) {
        int d = Dist(u, x);
        if (lim >= d) {
        	add(X, in[u][min(mxd[u], lim - d)]);
        	add(out[u][min(mxd[u], lim - d)], X);
        }
        u = fa[u];
    }
}
#undef int
vector<long long> find_spread(int N, int M, vector<int> A, vector<int> B, vector<int> P, vector<int> D, vector<int> C) {
#define int long long
	memset(dist, 63, sizeof dist);
	ncnt = N + M;
	for (int i = 0; i < N - 1; i++) G[A[i] + 1].emplace_back(B[i] + 1), G[B[i] + 1].emplace_back(A[i] + 1);
	for (int i = 0; i < N; i++) c[i + 1] = C[i];
	vector<int> ret;
    dfs1(1, 0, 1);
    dfs2(1, 1);
    all = N, mnv = N + 1;
    getroot(1, 0);
    dfs3(rt);
    // for (int i = 1; i <= N; i++) cerr << fa[i] << " ";
    // cerr << "\n";
    for (int i = 1; i <= M; i++) Add(P[i - 1] + 1, D[i - 1], i + N);
    q.push((node) { N + 1, dist[N + 1] = 0 });
	dijkstra();
	for (int i = 1; i <= M; i++) {
		if (dist[N + i] >= inf) 
			ret.emplace_back(-1);
		else 
			ret.emplace_back(dist[N + i]);
	}
	return ret;
}

T4

2552 樹

首先不難想到任意定根將樹分層,然後只需要確定相鄰兩層間的祖先關係。假設當前父親層點集為 \(A\),兒子層點集為 \(B\),我們隨機將 \(A\) 平均分成兩半,設其中一半為 \(A_r\)。則 \(A\) 中一個點 \(i\)\(B\) 中點 \(j\) 的父親當且僅當 \(ask(i, A_r) + |A_r| = ask(j, A_r)\)。這樣就可以將 \(A\) 中點按 \(ask(i, A_r)\) 分組,將 \(B\) 中點按 \(ask(j, A_r)\) 分組,則所有祖先關係進一步被確定在兩層的對應組之間。因此再對於每一組遞迴求解即可。詢問次數 \(\mathcal{O}(n \log n)\),最壞情況不超過九千多。

程式碼
#include "tree.h"
#include <iostream>
#include <random>
#include <vector>
using namespace std;
random_device rd;
mt19937 mtrand(rd());
int fa[1005], dep[1005];
vector<int> vec[1005];
inline int dist(int x, int y) {
	int ret = 0;
	while (x ^ y) (dep[x] < dep[y]) ? (y = fa[y]) : (x = fa[x]), ++ret;
	return ret;
}
void Solve(vector<int> A, vector<int> B) {
	if (!A.size()) 
		return;
	if ((int)A.size() == 1) {
		for (auto v : B) answer(v, fa[v] = A[0]);
		return;
	}
	vector<int> Ar;
	shuffle(A.begin(), A.end(), mtrand);
	for (int i = 0; i * 2 < (int)A.size(); i++) Ar.emplace_back(A[i]);
	map<int, vector<int> > mp1, mp2;
	for (auto v : A) {
		int tmp = 0;
		for (auto w : Ar) tmp += dist(v, w) + 1;
		mp1[tmp].emplace_back(v);
	}
	for (auto v : B) mp2[ask(v, Ar)].emplace_back(v);
	for (auto v : mp2) Solve(mp1[v.first], v.second);
}
void solver(int n, int A, int B) {
	int rt = mtrand() % n + 1;
	for (int i = 1; i <= n; i++) vec[dep[i] = ask(rt, (vector<int>) { i })].emplace_back(i);
	for (int i = 0; i <= n; i++) Solve(vec[i], vec[i + 1]);
}