[赛记] 暑假集训CSP提高模拟26

这场rank4,应该是暑假以来打的最好的一场了。。。
其它时候就没进过前10。。。

博弈 30pts

赛时 O(n2) 暴力30pts;

对于暴力,我们能发现一个性质就是只要有一类边权出现了奇数次,那么先手必胜,所以我们枚举每一个点对,开个数组统计一下即可;

不要忘了离散化;

对于正解,用到了一个东西:xorhashing

其实对于这种判断奇偶的问题,我们很容易想到 xor,因为当有偶数个相同的数出现时, 他们 xor 起来的值是为 0 的,所以我们只需判断树上任意两点间的路径异或和是否为 0 即可;

那么我们做一下树上差分,记一下从根(指定一个)到每个点的异或和,然后找一下相同的减去其贡献即可(这是根据异或的性质:两个相同的数异或值为 00xorx=x);

但是我们发现这样并不能保证正确性,比如 1xor2xor3=0,但这种情况是先手必胜,判断出来是后手必胜,出现了冲突;

发现这其实很像 hash,所以我们可以给每类边权随一个在 unsigned long long 范围内的值,这样正确判断的概率就很大了;

随权值的操作可以用mt19937_64 来实现;

其实思路过程可能应该是点分治,不行再到树上差分,再到异或,再到 xorhashing(当然赛时可能到不了);

这应该就是 xorhashing 的基本应用了;

点击查看代码
#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;
}
posted @   Peppa_Even_Pig  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示