这场rank4,应该是暑假以来打的最好的一场了。。。
其它时候就没进过前10。。。
博弈 30pts
赛时 $ O(n^2) $ 暴力30pts;
对于暴力,我们能发现一个性质就是只要有一类边权出现了奇数次,那么先手必胜,所以我们枚举每一个点对,开个数组统计一下即可;
不要忘了离散化;
对于正解,用到了一个东西:$ xor-hashing $;
其实对于这种判断奇偶的问题,我们很容易想到 $ xor $,因为当有偶数个相同的数出现时, 他们 $ xor $ 起来的值是为 $ 0 $ 的,所以我们只需判断树上任意两点间的路径异或和是否为 $ 0 $ 即可;
那么我们做一下树上差分,记一下从根(指定一个)到每个点的异或和,然后找一下相同的减去其贡献即可(这是根据异或的性质:两个相同的数异或值为 $ 0 $, $ 0 \operatorname{xor} x = x $);
但是我们发现这样并不能保证正确性,比如 $ 1 \operatorname{xor} 2 \operatorname{xor} 3 = 0 $,但这种情况是先手必胜,判断出来是后手必胜,出现了冲突;
发现这其实很像 $ hash $,所以我们可以给每类边权随一个在 $ unsigned \ long \ long $ 范围内的值,这样正确判断的概率就很大了;
随权值的操作可以用mt19937_64
来实现;
其实思路过程可能应该是点分治,不行再到树上差分,再到异或,再到 $ xor-hashing $(当然赛时可能到不了);
这应该就是 $ xor-hashing $ 的基本应用了;
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <map>
#include <ctime>
#include <cstdlib>
#include <random>
using namespace std;
int t;
int n;
int x[5000005], y[500005], w[500005], b[500005];
unsigned long long p[500005];
unsigned long long ha[500005];
map<unsigned long long, int> sum;
map<unsigned long long, bool> vis;
struct sss{
int t, ne;
unsigned long long w;
}e[1000005];
int h[1000005], cnt;
void add(int u, int v, unsigned long long ww) {
e[++cnt].t = v;
e[cnt].ne = h[u];
h[u] = cnt;
e[cnt].w = ww;
}
void dfs(int x, int fa) {
sum[ha[x]]++;
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == fa) continue;
ha[u] = ha[x] ^ e[i].w;
dfs(u, x);
}
}
long long ans;
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
mt19937_64 ran(time(0));
cin >> t;
for (int i = 1; i <= 500000; i++) {
p[i] = ran();
}
while(t--) {
cin >> n;
cnt = 0;
ans = 0;
memset(h, 0, sizeof(h));
memset(ha, 0, sizeof(ha));
sum.clear();
vis.clear();
for (int i = 1; i <= n - 1; i++) {
cin >> x[i] >> y[i] >> w[i];
b[i] = w[i];
}
sort(b + 1, b + 1 + n - 1);
int len = unique(b + 1, b + 1 + n - 1) - b - 1;
for (int i = 1; i <= n - 1; i++) {
add(x[i], y[i], p[lower_bound(b + 1, b + 1 + len, w[i]) - b]);
add(y[i], x[i], p[lower_bound(b + 1, b + 1 + len, w[i]) - b]);
}
dfs(1, 0);
for (int i = 1; i <= n; i++) {
if (sum[ha[i]] && !vis[ha[i]]) {
vis[ha[i]] = true;
ans += 1ll * (sum[ha[i]] * (sum[ha[i]] - 1) / 2);
}
}
cout << 1ll * n * (n - 1) / 2 - ans << '\n';
}
return 0;
}
大陆 100pts
赛时切了,还是首A。。。
其实这个题就是模拟一下即可;
考虑从下往上删子树,因为各个子树互不影响;
那么当我们回溯时,判断一下当前子树是否符合要求,若符合,则直接删掉;
这样我们会发现一个问题,就是可能会剩下一些点,这些点都和根组成一个连通块,且大小小于b;
那么我们想要把它们并入一个其它的,已划分出的连通块去,那么这个连通块的大小要小于等于2倍的b;
所以我们把划分条件改一下,当一棵子树的大小大于等于b且小于等于2倍的b时,就把它划出去;
那这样我们还会出现另一个问题,就是当一棵子树的子树很多时,可能出现这棵子树的大小大于2倍的b,但它的任意一棵子树的大小都小于b的情况,对于这种情况,我们直接单独考虑,每b个划分一次,不足b个的并入先前的连通块中,这样就满足要求了;
这样的话,我们划分出的最后一个连通块可能会大于2倍的b,小于3倍的b,这样就不能使根节点连的块并入此块,所以我们将这颗子树的根并入第一个连通块,因为第一个连通块大小是小于等于2倍的b的,这样就可以使根节点连的块并入第一个块了;
这题还是可以练练搜索能力的;
点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
int n, b;
struct sss{
int t, ne;
}e[50005];
int h[50005], cnt;
void add(int u, int v) {
e[++cnt].t = v;
e[cnt].ne = h[u];
h[u] = cnt;
}
int siz[50005];
int c[50005], tot, cap[50005], sum[50005], fa[50005];
int aa;
void dfs(int x, int f) {
siz[x] = 1;
fa[x] = f;
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == f) continue;
dfs(u, x);
siz[x] += siz[u];
}
}
void cfs(int x) {
c[x] = tot;
sum[tot]++;
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == fa[x] || c[u]) continue;
cfs(u);
}
}
void efs(int x) {
int su = 0;
int ssu = 0;
tot++;
cap[tot] = x;
sum[tot]++;
c[x] = tot; //并入第一个块;
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == fa[x]) continue;
if (c[u]) continue;
su += siz[u];
ssu += siz[u];
cfs(u);
if (su >= b) {
tot++;
if (siz[x] - ssu - 1 < b) {
tot--;
}
cap[tot] = x;
su = 0;
}
}
}
void afs(int x) {
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == fa[x]) continue;
afs(u);
}
if (siz[x] >= b && siz[x] <= 2 * b) {
tot++;
cap[tot] = x;
cfs(x);
for (int i = fa[x]; i; i = fa[i]) {
siz[i] -= siz[x];
}
} else if (siz[x] > 2 * b) {
efs(x);
for (int i = fa[x]; i; i = fa[i]) {
siz[i] -= siz[x];
}
}
}
void ddfs(int x, int fa) {
if (c[x]) {
aa = c[x];
return;
}
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == fa) continue;
ddfs(u, x);
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> b;
int x, y;
for (int i = 1; i <= n - 1; i++) {
cin >> x >> y;
add(x, y);
add(y, x);
}
dfs(1, 0);
if (siz[1] >= b && siz[1] <= 3 * b) {
cout << 1 << '\n';
for (int i = 1; i <= n; i++) {
cout << 1 << ' ';
}
cout << '\n';
cout << 1;
return 0;
}
afs(1);
if (!c[1]) {
aa = 0;
ddfs(1, 0);
for (int i = 1; i <= n; i++) {
if (!c[i]) c[i] = aa;
}
}
cout << tot << '\n';
for (int i = 1; i <= n; i++) {
cout << c[i] << ' ';
}
cout << '\n';
for (int i = 1; i <= tot; i++) {
cout << cap[i] << ' ';
}
return 0;
}