OI笔记 | 2022.12 做题记录(一)

半个月没放做题记录了。清一下云剪贴板。

[NOIP2003 提高组] 加分二叉树

洛谷 P1040

设一个 $n$ 个节点的二叉树 $\text{tree}$ 的中序遍历为$(1,2,3,\ldots,n)$,其中数字 $1,2,3,\ldots,n$ 为节点编号。每个节点都有一个分数(均为正整数),记第 $i$ 个节点的分数为 $d_i$,$\text{tree}$ 及它的每个子树都有一个加分,任一棵子树 $\text{subtree}$(也包含 $\text{tree}$ 本身)的加分计算方法如下:

$\text{subtree}$ 的左子树的加分 $\times$ $\text{subtree}$ 的右子树的加分 $+$ $\text{subtree}$ 的根的分数。

若某个子树为空,规定其加分为 $1$,叶子的加分就是叶节点本身的分数。不考虑它的空子树。

试求一棵符合中序遍历为 $(1,2,3,\ldots,n)$ 且加分最高的二叉树 $\text{tree}$。要求输出

  1. $\text{tree}$ 的最高加分。

  2. $\text{tree}$ 的前序遍历。

题解

考虑区间 dp。如果中序遍历是一个 $(x\dots y)$ 的排列,那么枚举根节点 $k$,可以设 $(x\dots k-1)$ 为左子树, $(k+1\dots y)$ 为右子树,加分即为 $dp(x,k-1)\times dp(k+1,y)+dp(k,k)$。从较小的区间开始转移,故答案即为 $dp(1,n)$。输出前序遍历只要在 $dp$ 时记录即可。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 40;
int n, dp[MAX_N][MAX_N], r[MAX_N][MAX_N];
inline ll read() {...}
char buf[50];
inline void write(ll x) {...}
void print(int s, int t) {
	write(r[s][t]), putchar(' ');
	if(s == t)	return;
	if(s <= r[s][t] - 1)	print(s, r[s][t] - 1);
	if(r[s][t] + 1 <= t)	print(r[s][t] + 1, t);
}

int main() {
	n = read();
	for(int i = 1; i <= n; i++) 
		dp[i][i] = read(), dp[i][i - 1] = 1, r[i][i] = i;
	for(int len = 1; len <= n; len++) {
		for(int i = 1; i + len <= n; i++) {
			int j = i + len;
			dp[i][j] = dp[i + 1][j] + dp[i][i];
			r[i][j] = i;
			for(int k = i + 1; k < j; k++) {
				if(dp[i][j] < dp[i][k - 1] * dp[k + 1][j] + dp[k][k]) {
					dp[i][j] = dp[i][k - 1] * dp[k + 1][j] + dp[k][k];
					r[i][j] = k;
				}
			}
		}
	}
	write(dp[1][n]), putchar('\n');
	print(1, n);
	return 0;
}

划分数列

洛谷 U264732

给定一个正整数 $n$,请你考虑下面这个长度为 $n$ 的数列:

\[\varphi(1),\varphi(2),\varphi(3),\dots\varphi(n)\]

其中 $\varphi(x)$ 为欧拉函数,定义为小于或等于 $x$ 且与 $x$ 互质的正整数的个数,例如 $\varphi(1)=\varphi(2)=1$。

判断是否可以把这个数列分成和相等的两组,如果可以,输出任意一种划分方案。

$1\le n\le 5×10^6$

题解

自己造的第一个题。原 MO 题是 2022 北大金秋营的一题:

求证:对任意整数 $n>1,\varphi(1),\varphi(2),\dots\varphi(n)$ 可以分成和相等的两组。

结论来看,我们这题只有 $n=1$ 的情况不能找到划分方案。接下来我们来证明一下原题,证明的过程中也就得到了划分的方案。

考虑直接证明一个更强的结论

引理:对任意整数 $n>1,a_1,a_2,\dots a_n\in \mathbb{Z_+},a_i\le i$ 且 $\sum\limits_{i=1}^n a_i$ 为偶数,则数列 $a$ 可以分成和相等的两组。

引理证明

设 $A$ 组内的和为 $S_A $,$B$ 组内的和为 $S_B$。

首先我们把 $a_n$ 放入 $A$ 组, $a_{n-1}$ 放入 $B$ 组。则由于 $1\le a_n\le n$ 且 $1\le a_{n-1}\le n-1$,可知 $0\le \lvert S_A-S_B\rvert \le n - 1$。

数学归纳法。假设已经放好了 $a_n,a_{n-1}\dots a_{i+1}$,且满足 $0\le \lvert S_A-S_B\rvert \le i + 1$。 现在考虑放 $a_{i}$,分类讨论:

  1. 若此时 $S_A=S_B$,我们随意放入一个组内,假设放入 $B$ 中。那么新的差的绝对值会等于 $ \lvert S_A-(S_B + a_i)\rvert = \lvert a_i\rvert$,由于 $1\le a_i\le i$,所以新的差的绝对值满足 $0\le \lvert S_A-(S_B + a_i)\rvert\le i$。

  2. 若此时 $S_A>S_B$,我们放入 $B$ 中。由于 $0< S_A-S_B\le i+1$ 且 $1\le a_i\le i$,则新的差 $-i<S_A-(S_B+a_i) \le i$,所以新的差的绝对值也满足 $0\le \lvert S_A-(S_B + a_i)\rvert\le i$。

  3. 若此时 $S_A<S_B$,同 $2$ 可证放入 $A$ 中能使得新的差的绝对值也满足 $0\le \lvert S_B-(S_A + a_i)\rvert\le i$

所以我们可以从考虑 $a_n\dots a_{i+1}$ 时的 $0\le \lvert S_A-S_B\rvert \le i + 1$ 推出考虑 $a_n\dots a_i$ 时的 $0\le \lvert S_A-S_B\rvert \le i$。

所以考虑 $a_n\dots a_1$ 时的 $0\le \lvert S_A-S_B\rvert \le 1$。又因为 $S_A+S_B$ 为偶数,所以有 $S_A=S_B$。

事实上,这就是 OIer 说的不断贪心地把当前的数加到和较小的组上,能使得两组的和最平均的依据。

那么这题就很显然了,显然 $1$ 到 $n$ 的欧拉函数之和一定为偶数。用线性筛 $O(n)$ 求出所有 $1$ 到 $n$ 的欧拉函数,然后贪心地分组即可。注意开 long long

std.cpp

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 1e7 + 10;

inline ll read() {...}
inline void write(ll x) {...}

bool vis[MAX_N];
int phi[MAX_N], primes[MAX_N], cnt = 0;
void init(int n) {
    for(int i = 1; i <= n; i++)
        vis[i] = 1;
    vis[1] = 0, phi[1] = 1;
    for(int i = 2; i <= n; i++) {
        if(vis[i]) {
            primes[++cnt] = i;
            phi[i] = i - 1;
        }
        for(int j = 1; j <= cnt && i * primes[j] <= n; j++) {
            vis[i * primes[j]] = 0;
            if(i % primes[j] == 0) {
                phi[i * primes[j]] = primes[j] * phi[i];
                break;
            }
            else    phi[i * primes[j]] = phi[primes[j]] * phi[i];
        }
    }
}
vector<int> A, B;
ll sumA, sumB;
int main() {
    int n = read();
    init(n);
    if(n == 1) {
        puts("NO");
        return 0;
    }
    A.push_back(phi[n]), B.push_back(phi[n - 1]);
    sumA += phi[n], sumB += phi[n - 1];
    for(int i = n - 2; i >= 1; i--) {
        if(sumA >= sumB) {
            B.push_back(phi[i]);
            sumB += phi[i];
        }
        else {
            A.push_back(phi[i]);
            sumA += phi[i];
        }
    }
    puts("YES");
    write(A.size()), putchar(' '), write(B.size()), putchar('\n');
    for(int x : A)  write(x), putchar(' ');
    putchar('\n');
    for(int x : B)  write(x), putchar(' ');
    putchar('\n');
    return 0;
}

gen.py

from cyaron import *

_n = ati([0, 200, 600, 1000, 1, 10000, 100000, 5e5, 1e6, 5e6, 5e6])

for i in range(1, 11):
    io = IO(file_prefix = "seq", data_id = i)
    if(_n[i] == 1):
        n = 1
    else:
        n = randint(_n[i - 1], _n[i])
    io.input_writeln(n)
    io.output_gen("std.exe")

[NOIP2006 提高组] 能量项链

洛谷 P1063

在 Mars 星球上,每个 Mars 人都随身佩带着一串能量项链。在项链上有 $N$ 颗能量珠。能量珠是一颗有头标记与尾标记的珠子,这些标记对应着某个正整数。并且,对于相邻的两颗珠子,前一颗珠子的尾标记一定等于后一颗珠子的头标记。因为只有这样,通过吸盘(吸盘是 Mars 人吸收能量的一种器官)的作用,这两颗珠子才能聚合成一颗珠子,同时释放出可以被吸盘吸收的能量。如果前一颗能量珠的头标记为 $m$,尾标记为 $r$,后一颗能量珠的头标记为 $r$,尾标记为 $n$,则聚合后释放的能量为 $m \times r \times n$(Mars 单位),新产生的珠子的头标记为 $m$,尾标记为 $n$。

需要时,Mars 人就用吸盘夹住相邻的两颗珠子,通过聚合得到能量,直到项链上只剩下一颗珠子为止。显然,不同的聚合顺序得到的总能量是不同的,请你设计一个聚合顺序,使一串项链释放出的总能量最大。

$4 \le N \le 100$

题解

断环成链,然后就是区间 dp 的板子。

$dp(l,r)$ 表示从第 $l$ 个项链到第 $r$ 个项链的最大能量,然后枚举断开点 $k$,则有:

$dp(l,r) = \max(dp(l,k)+dp(k,r)+a_l\times a_k \times a_r)$

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 210;
int dp[MAX_N][MAX_N], a[MAX_N];

inline ll read() {...}
char buf[50];
inline void write(ll x) {...}

int main() {
	int n = read(), ans = 0;
	for(int i = 1; i <= n; i++)
		a[i] = read(), a[i + n] = a[i];
	for(int len = 1; len <= n; len++) {
		for(int l = 1; l + len <= 2 * n; l++) {
			int r = l + len;
			for(int k = l + 1; k <= r - 1; k++)
				dp[l][r] = max(dp[l][r], dp[l][k] + dp[k][r] + a[l] * a[k] * a[r]);
		}
	}
	for(int i = 1; i <= n; i++)
		ans = max(ans, dp[i][i + n]);
	write(ans);
	return 0;
}

[USACO16OPEN]262144 P

洛谷 P3147

Bessie 喜欢在手机上下游戏玩,然而她蹄子太大,很难在小小的手机屏幕上面操作。

她被她最近玩的一款游戏迷住了,游戏一开始有 $n$ 个正整数,($2\le n\le 262144$),范围在 $1\sim40$。在一步中,贝西可以选相邻的两个相同的数,然后合并成一个比原来的大一的数(例如两个 $7$ 合并成一个 $8$),目标是使得最大的数最大,请帮助 Bessie 来求最大值。

题解

考虑一个玄妙的状态设计: $dp(i,j)$ 表示以 $j$ 为起点,合成出 $i$ 的右端下标,则:

$dp(i,j)=dp(i-1,dp(i-1,j))$

类似于倍增。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 3e5 + 10, MAX_M = 60;
int a[MAX_N], dp[MAX_M][MAX_N];

inline ll read() {...}
char buf[50];
inline void write(ll x) {...}

int main() {
	int n = read(), ans = 1;
	for(int i = 1; i <= n; i++)
		a[i] = read(), dp[a[i]][i] = i + 1;		
	for(int i = 2; i <= 58; i++) {
		for(int j = 1; j <= n; j++) {
			if(!dp[i][j])	dp[i][j] = dp[i - 1][dp[i - 1][j]];
			if(dp[i][j])	ans = i; 
		}
	}
	write(ans);
	return 0;
}

TWINSNOW - Snowflakes

SP4354

你可能听说过,没有两片雪花是相同的。你要写一个程序,判断这是不是真的。你的程序会读到一些有关于这些雪花的信息,找到一对完全相同的雪花。每片雪花有六个角。对于每个雪花,你的程序会获得每个角的长度。我们认为两片雪花相同,当且仅当它们各自从某一个角开始,逆时针或顺时针记录长度,能得到两个相同的六元组。

题解

对于一个雪花,记录它的所有数字和 $sum_i\bmod 99991$,作为它的特征值(hash)。

然后对与这个雪花特征值相同的所有雪花暴力匹配。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 2e5, MOD = 99991;
int a[MAX_N][6], sum[MAX_N];
vector<int> f[MOD + 5];

inline ll read() {...}
char buf[50];
inline void write(ll x) {...}

bool check(int x, int y) {
	for(int i = 0; i <= 5; i++) {
		bool flag = 1;
		for(int k = 0; k <= 5; k++) {
			if(a[x][k] != a[y][(i + k) % 6]) {
				flag = 0;
				break;
			}
		}
		if(flag)	return 1;
		flag = 1;
		for(int k = 0; k <= 5; k++) {
			if(a[x][k] != a[y][(i + 6 - k) % 6]) {
				flag = 0;
				break;
			}
		}
		if(flag)	return 1;
	}
	return 0;
}


int main() {
	int n = read();
	for(int i = 1; i <= n; i++)
		for(int j = 0; j <= 5; j++)
			a[i][j] = read(), (sum[i] += a[i][j]) %= MOD;
	for(int i = 1; i <= n; i++) {
		for(int k : f[sum[i]])
			if(check(i, k)) {
				puts("Twin snowflakes found.");
				return 0;	
			}
		f[sum[i]].push_back(i);
	}
	puts("No two snowflakes are alike.");
	return 0;
}

平面最近点对

洛谷 P7883

给定 $n$ 个二维欧几里得平面上的点 $p_1, p_2, \dots, p_n$,请输出距离最近的两个点的距离。

$2 \leq n \leq 4 \times 10^5$,$-10^7 \leq x_i, y_i \leq 10^7$。

题解

我们可以通过分治解决这个问题。

假设函数 $\operatorname{solve}(l,mid)$ 和 $\operatorname{solve}(mid,r)$ 已经分别处理完了 $[l,mid)$ 和 $[mid, r)$ 两个区间内部的最近点对,现在考虑如何快速合并答案。

我们先把这个两个区间内的点以纵坐标为关键字做归并排序。

考虑什么样的点会分居在这两个区间内,且可能对答案产生贡献。设这样的点为 $T(x_0,y_0)$ ,它首先要满足 $\lvert x_0 - x_{mid}\rvert \le ans$,否则它与另半边上的所有点的距离都大于 $ans$。其次它可能影响到的区间为 $(y_0, y_0 + ans]$,考虑我们按纵坐标排序了,所以可以双指针,对于每一个可能产生贡献的点,向上找到 $(y_0, y_0 + ans]$ 的所有点,然后计算 $A$ 到这些点的距离,更新答案即可。

从题解里学到了一个内置函数 inplace_merge(l, mid, r, cmp) 可以原地把 $[l,mid)$ 和 $[mid, r)$ 做归并排序。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<double, double> pii;
const int MAX_N = 4e5 + 10;
const double INF = 1e20;
int n;
double ans = INF;
pii p[MAX_N];
inline bool cmpx(pii x, pii y) {return x.first < y.first;}
inline bool cmpy(pii x, pii y) {return x.second < y.second;}

inline ll read() {...}

double dis(pii x, pii y) {
    double dx = 1.0 * x.first - y.first, dy = 1.0 * x.second - y.second;
    return sqrt(dx * dx + dy * dy);
}
void solve(int l, int r) {
    if(l + 1 >= r)  return;
    int mid = (l + r) >> 1, midx = p[mid].first;
    solve(l, mid), solve(mid, r);
    inplace_merge(p + l, p + mid, p + r, cmpy);
    vector<int> t;
    for(int i = l; i < r; i++)
        if((double)abs((double)midx - p[i].first) <= ans)    t.push_back(i);
    int i = 0, j = 0;
    for(; i < (int)t.size(); i++) {
        while(j < (int)t.size() && (double)p[t[j]].second <= (double)p[t[i]].second + ans)
            j++;
        for(int k = i + 1; k < j; k++)
            ans = min(ans, dis(p[t[i]], p[t[k]]));
    }
}

int main() {
    n = read();
    for(int i = 0; i < n; i++)
        scanf("%lf %lf", &p[i].first, &p[i].second);
    sort(p, p + n, cmpx);
    solve(0, n);
    printf("%.4lf\n", ans);
    return 0;
}

由乃救爷爷

洛谷 P3793

给定一个 $n$ 个数的数列,有 $m$ 次询问。

每次询问给出两个随机数 $l,r$,求 $\max\limits_{i=l}^r a_i$。

$n,m \le 2\times 10^7$,时间限制 $5s$,空间限制 500.00MB。

题解

分块 ST 表的模板题。考虑到时间和空间,不可能直接对整个数列 $O(n\log n)$ 预处理 ST 表。

考虑分块。我们把数列分为 $\sqrt{n}$ 块,对于每块内的最大值建 ST 表,并且求出块内的前缀最大值和后缀最大值。这部分的复杂度为 $O(n+\sqrt n \log \sqrt n)$。

询问时,如果 $l, r$ 不在同一块,则 $l,r$ 之间的整块的最大值可以 ST 表查询,散块的最大值可以用前缀后缀最大值查询,时间复杂度 $O(1)$。如果 $l,r$ 在同一块内,只能暴力计算,时间复杂度 $O(\sqrt n)$。

然后由于数据随机,可以算出期望时间复杂度为 $O(n)$。但是我不会算期望就不算了,反正能过。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int MAX_N = 2e7 + 10, MAX_LEN = 4500, MAX_LOG = 15;
int a[MAX_N], pre[MAX_N], suf[MAX_N], siz;
struct st {
    int mx[MAX_LEN][MAX_LOG], log_2[MAX_LEN], len;
    st() {memset(mx, 0, sizeof(mx));}
    void init() {
        log_2[1] = 0, log_2[2] = 1;
        for(int i = 3; i <= len; i++)
            log_2[i] = log_2[i >> 1] + 1;
        for(int j = 1; j < MAX_LOG; j++)
            for(int i = 1; i + (1 << (j - 1)) - 1 <= len; i++)
                mx[i][j] = max(mx[i][j - 1], mx[i + (1 << (j - 1))][j - 1]);
    }
    int query(int l, int r) {
        int x = log_2[r - l + 1];
        return max(mx[l][x], mx[r - (1 << x) + 1][x]);
    }
} ST;

// -- 随机数生成器和读入输出优化 --
namespace GenHelper{...}
void srand(unsigned x){...}
int read(){...}
inline ll readint() {...}
inline void write(ull x) {...}
// ------------------------------

inline int bl(int x) {return (x - 1) / siz + 1;};

int main() {
    int n = readint(), m = readint(), s = readint();
    srand(s);
    siz = sqrt(n);
    for(int i = 1; i <= n; i++)
        a[i] = read();
    ST.len = bl(n);
    for(int i = 1; i <= bl(n); i++) {
        int L = (i - 1) * siz + 1, R = min(i * siz, n);
        pre[L] = a[L], suf[R] = a[R];
        for(int j = L; j <= R; j++) {
            ST.mx[i][0] = max(ST.mx[i][0], a[j]);
            if(j > L)  pre[j] = max(pre[j - 1], a[j]);
        }
        for(int j = R - 1; j >= L; j--)
            suf[j] = max(suf[j + 1], a[j]);
    }
    ST.init();
    ull ans = 0;
    while(m--) {
        int l = read() % n + 1, r = read() % n + 1;
        if(l > r)   swap(l, r);
        int t = 0;
        if(bl(l) == bl(r)) {
            for(int i = l; i <= r; i++)
                t = max(t, a[i]);
        }
        else {
            if(bl(l) + 1 <= bl(r) - 1)  t = max(t, ST.query(bl(l) + 1, bl(r) - 1));
            t = max(t, suf[l]);
            t = max(t, pre[r]);
        }
        ans += t;
    }
    write(ans);
    return 0;
}

XOR on Segment

CF242E

给定 $n$ 个数的序列 $a$。$m$ 次操作,操作有两种:

1 l r :求 $\displaystyle\sum_{i=l}^r a_i$。

2 l r x :把 $a_l\sim a_r$ 异或上 $x$。

$1\le n\le 10^5$,$1\le m\le 5\times 10^4$,$0\le a_i\le 10^6$,$1\le x\le 10^6$。

题解

考虑在线段树的每个节点上维护数组 $cnt$,其中 $cnt_i$ 为区间内数的二进制为 $i$ 的个数。

对于查询操作,答案即为查询 $[l,r]$ 上节点的 $\sum\limits_{i=0}^{19} 2^i\times cnt_i$。

对于修改操作:如果 $x$ 的二进制表示的第 $i$ 位为 $1$,则 $cnt_i\gets (t-s+1)-cnt_i$,其中 $s,t$ 为节点表示的区间。否则不操作。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 1e5 + 10;

int a[MAX_N];
struct node {
	int cnt[20], tag;
	node() {memset(cnt, 0, sizeof(cnt)); tag = 0;}
} d[MAX_N << 2];

inline ll read() {...}
char buf[50];
inline void write(ll x) {...}

inline int lc(int p) {return (p << 1);}
inline int rc(int p) {return (p << 1) | 1;}
inline int mid(int s, int t) {return s + ((t - s) >> 1);}
inline void pu(int p) {
	for(int i = 0; i < 20; i++)
		d[p].cnt[i] = d[lc(p)].cnt[i] + d[rc(p)].cnt[i];
}
inline void change(int p, int x, int s, int t) {
	for(int i = 0; i < 20; i++)
		if(x & (1 << i))	d[p].cnt[i] = (t - s + 1) - d[p].cnt[i];
}
inline void pd(int p, int s, int t) {
	int m = mid(s, t);
	change(lc(p), d[p].tag, s, m), change(rc(p), d[p].tag, m + 1, t);
	d[lc(p)].tag ^= d[p].tag;
	d[rc(p)].tag ^= d[p].tag;
	d[p].tag = 0;
}

void build_tree(int s, int t, int p) {
	if(s == t) {
		for(int i = 0; i < 20; i++)
			if(a[s] & (1 << i))	d[p].cnt[i]++;
		return;
	}
	int m = mid(s, t);
	build_tree(s, m, lc(p));
	build_tree(m + 1, t, rc(p));
	pu(p);
}

ll query(int s, int t, int p, int l, int r) {
	if(l <= s && t <= r) {
		ll ret = 0;
		for(int i = 0; i < 20; i++)
			ret += (1ll << i) * d[p].cnt[i];
		return ret;
	}
	pd(p, s, t);
	int m = mid(s, t);
	ll ret = 0;
	if(l <= m)	ret += query(s, m, lc(p), l, r);
	if(r > m)	ret += query(m + 1, t, rc(p), l, r);
	return ret;	
}

void update(int s, int t, int p, int l, int r, int x) {
	if(l <= s && t <= r) {
		change(p, x, s, t);
		d[p].tag ^= x;
		return;
	}
	pd(p, s, t);
	int m = mid(s, t);
	if(l <= m)	update(s, m, lc(p), l, r, x);
	if(r > m)	update(m + 1, t, rc(p), l, r, x);
	pu(p);
}

int main() {
	int n = read();
	for(int i = 1; i <= n; i++)
		a[i] = read();
	build_tree(1, n, 1);
	int m = read();
	while(m--) {
		int op = read(), l = read(), r = read(), x;
		if(op == 1)	write(query(1, n, 1, l, r)), putchar('\n');
		else	x = read(), update(1, n, 1, l, r, x);
	}
	return 0;
}

最长异或路径

洛谷 P4551

给定一棵 $n$ 个点的带权树,结点下标从 $1$ 开始到 $n$。寻找树中找两个结点,求最长的异或路径。

异或路径指的是指两个结点之间唯一路径上的所有边权的异或。

题解

定义 $sum_i$ 为节点 $i$ 到根节点的异或和,可以 $O(n)$ 处理出来。

则 $x$ 到 $y$ 的路径异或和为 $sum_x \operatorname{xor} sum_y$。

然后我们对所有 $sum_i$ 建 01trie,然后对于每一个 $sum_i$,在 01trie 上跑一遍按位贪心即可。具体来说,如果当前可选的路径中有与 $sum_i$ 的这一位不一样的,就往那边走。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 1e5 + 10;
struct edge {int u, v, w;};
vector<edge> edges;
vector<int> G[MAX_N];
int tree[MAX_N << 5][2], sum[MAX_N], cnt;
void AddEdge(int u, int v, int w) {
	edges.push_back({u, v, w});
	G[u].push_back(edges.size() - 1);
}

inline ll read() {...}
char buf[50];
inline void write(ll x) {...}

void dfs(int u, int pre) {
	for(int i : G[u]) {
		int v = edges[i].v, w = edges[i].w;
		if(v == pre)	continue;
		sum[v] = sum[u] ^ w;
		dfs(v, u);
	}
}
void build(int val) {
	int now = 0;
	for(int i = 30; i >= 0; i--) {
		bool f = (1 << i) & val;
		if(!tree[now][f])	tree[now][f] = ++cnt;
		now = tree[now][f];
	}
}
int query(int val) {
	int now = 0, ret = 0;
	for(int i = 30; i >= 0; i--) {
		bool f = (1 << i) & val;
		if(tree[now][f ^ 1])	ret += (1 << i), now = tree[now][f ^ 1];
		else	now = tree[now][f];
	}
	return ret;
}
int main() {
	int n = read(), ans = 0;
	for(int i = 1; i <= n - 1; i++) {
		int u = read(), v = read(), w = read();
		AddEdge(u, v, w);
	}
	dfs(1, 0);
	for(int i = 1; i <= n; i++)
		build(sum[i]);
	for(int i = 1; i <= n; i++)
		ans = max(ans, query(sum[i]));
	write(ans);
	return 0;
}

基数排序

基数排序,即按照从小到大的数位排序。

首先我们把个位为 $i$ 的数推入数组 $G_i$ 中,然后枚举 $i=0\sim 9$ 把 $G_i$ 中所有数取出,完成这一步排序。然后依次做十位和百位。

对于 int 范围内的数,其实我们可以转为 $2^{16}$ 进制,这样只用对两位排序,再转回去即可。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int MAX_N = 1e5 + 10, BASE = (1 << 16);
pii num[MAX_N];
vector<pii> G[BASE];

inline ll read() {...}
char buf[50];
inline void write(ll x) {...}

int main() {
	int n = read();
	for(int i = 1; i <= n; i++) {
		int x = read();
		num[i].first = x / BASE, num[i].second = x % BASE;
	}
	for(int i = 1; i <= n; i++)
		G[num[i].second].push_back(num[i]);
	int tot = 0;
	for(int i = 0; i < BASE; i++) {
		for(pii x : G[i])
			num[++tot] = x;
		G[i].clear();
	}
	for(int i = 1; i <= n; i++)
		G[num[i].first].push_back(num[i]);
	tot = 0;
	for(int i = 0; i < BASE; i++)
		for(pii x : G[i])
			num[++tot] = x;
	for(int i = 1; i <= n; i++)
		write(num[i].first * BASE + num[i].second), putchar(i == n ? '\n' : ' ');
	return 0;
}

Joy © 2023 Powered by Jekyll and Theme by solid

今日诗词API Valine 评论管理 Github