冲刺省选3月12日第三十场

冲刺省选3月12日第三十场

听歌听迷糊了

题目背景

风影剑醉:
今天地理都讲啥了啊

EAM:
反正没什么成本

EAM:
今天周六

风影剑醉:

风影剑醉:
我过迷糊了

风影剑醉:
[对不起]

EAM:
[动画表情]

风影剑醉:
现在有点迷糊

EAM:
[嗨起来]

EAM:
唱会歌啥的

风影剑醉:
我唱歌五音不全

EAM:
或者跳个舞

风影剑醉:
我倒是一直听歌

EAM:
[跳舞]

EAM:
[动画表情]

EAM:
[动画表情]

EAM:
[动画表情]

EAM:
[动画表情]

EAM:
[动画表情]

EAM:
[动画表情]

风影剑醉:
你会唱歌嘛

EAM:
不会(๑ó﹏ò๑)

EAM:
我倒是一直听歌

风影剑醉:
这话我听过

EAM:
这话我也听过

风影剑醉:
你喜欢听啥歌啊

风影剑醉:
就?流行的

EAM:

EAM:
基本都是

风影剑醉:
我也差不多吧

EAM:
[听歌]

风影剑醉:

风影剑醉:
你怎么什么都有表情包

EAM:
还挺像

风影剑醉:
好家伙

风影剑醉:
[懊恼]

EAM:
[哈]

风影剑醉:
[讨厌]

EAM:
[略略]

题目

题目描述

我们定义\(f(a_1,a_2,\cdots,a_n)\)\(\{a_1,a_2,\cdots,a_n\}\)的所有子序列异或和的总和。

比如\(f(1,2,3)=0+1+2+3+(1\oplus2)+(1\oplus3)+(2\oplus3)+(1\oplus2\oplus3)=12\)

求所有满足\(l\leq a_i\leq r\)\(f(a_1,a_2,\cdots,a_n)\)共有多少种不同的取值。

输入格式

本题包含多组测试数据。第一行一个整数\(T\)表示数据组数。

接下来\(T\)行,每行三个整数\(n,l,r\),表示一次询问。

输出格式

\(T\)行,每行一个整数表示该组询问的答案。

样例输入

3
3 3 4
2 10 11
3 1 3

样例输出

3
2
3

数据范围

对于\(10\%\)的数据,\(T\leq 5,n\leq 5,r\leq 5\)

对于\(30\%\)的数据,\(T\leq 50,n\leq 30,r\leq 100\)

对于\(50\%\)的数据,\(n\leq 100,l,r\leq 10^5\)

对于另外\(10\%\)的数据,\(n=1\)

对于\(100\%\)的数据,有\(T\leq10^5,n\leq 100,1\leq l\leq r\leq10^{18}\)

时间限制:1s

空间限制:256MB

AC代码
#include <bits/stdc++.h>
using std::cin;
using std::cout;
using std::vector;
using std::copy;
using std::reverse;
using std::swap;
using std::array;
using std::cerr;
using std::function;
using std::map;
using std::set;
using std::pair;
using std::mt19937;
using std::make_pair;
using std::tuple;
using std::make_tuple;
using std::uniform_int_distribution;
namespace qwq {
	mt19937 eng;
	void init(int Seed) {
		eng.seed(Seed);
		return;
	}
	int rnd(int l = 1, int r = 1000000000) {
		return uniform_int_distribution<int> (l, r)(eng);
	}
}
template <typename T>
inline T min(const T &x, const T &y) {
	return x < y ? x : y;
}
template<typename T>
inline T max(const T &x, const T &y) {
	return x > y ? x : y;
}
template <typename T>
inline T read() {
	T x = 0;
	bool f = 0;
	char ch = getchar();
	while (!isdigit(ch)) {
		f = ch == '-';
		ch = getchar();
	}
	while (isdigit(ch)) {
		x = x * 10 + ch - '0';
		ch = getchar();
	}
	return f ? -x : x;
}
#define O(x) cerr << #x << " : " << x << '\n'
const double Pi = acos(-1);
const int MAXN = 262144, MOD = 998244353, inv2 = (MOD + 1) / 2;
auto Ksm = [] (int x, int y) -> int {
	if (y < 0) {
		y %= MOD - 1;
		y += MOD - 1;
	}
	int ret = 1;
	for (; y; y /= 2, x = (long long) x * x % MOD) {
		if (y & 1) {
			ret = (long long) ret * x % MOD;
		} 
	}
	return ret;
};
auto Mod = [] (int x) -> int {
	if (x >= MOD) {
		return x - MOD;
	}
	else if (x < 0) {
		return x + MOD;
	}
	else {
		return x;
	}
};
inline int ls(int k) {
	return k << 1;
}
inline int rs(int k) {
	return k << 1 | 1;
}
long long solve(long long l, long long r) {
	if (l == r) {
		return 1;
	}
	else {
		long long x = l ^ r;
		x = (1ULL << (64 - __builtin_clzll(x))) - 1;
		l &= x;
		r &= x;
		long long ans = 0, m = 1ULL << (63 - __builtin_clzll(r)), l1 = m + l;
		// 对于选择都是 0 的,那么很ok啊,就是 [l,2^id)的数字
		ans = m - l;
		long long y = r ^ m;
		if (y) {
			// 找到 r 的次大 1
			// 那么这个后缀,都可以很简单的被取到是吧
			y = (1ULL << (64 - __builtin_clzll(y))) - 1;
		}
		long long r1 = y | m;
		if (r1 < l1) {
			// 如果不能随便取,那就是从 r1 到这个 l
			ans += r1 - l + 1;
		}
		else {
			// 这表示从 [m, 2 * m - 1] 都能取了
			ans += m;
		}
	return ans;
	}
}
int main() {
	freopen("a.in", "r", stdin);
	freopen("a.out", "w", stdout);
	std::ios::sync_with_stdio(0);
	cout << std::fixed << std::setprecision(8);
	cin.tie(0);
	cout.tie(0);
	qwq::init(20050112);
	// cout << (-3 / 2);
	int Q, N;
	long long l, r;
	cin >> Q;
	for (; Q--; ) {
		cin >> N >> l >> r;
		if (N == 1) {
			cout << r - l + 1 << '\n';
		}
		else {
			cout << solve(l, r) << '\n';
		}
	}
	return (0-0);
}

还是挺神奇的一道题目,首先我们考虑,对于一个确定的序列,他们的异或和的和是啥。

如果这一位里有一个1了,那么考虑选不选这个东西,那么这一位必定有一半的可能是 \(0\),一半可能是 \(1\)

那么,一个序列的答案就应该是 \((|^{n}_{i=1}a_i)\times 2 ^ {n-1}\)

然后我们就考虑,其实就是问,有 \(n\)\([l,r]\) 之间的数字,然后有多少种不同的或。

这个东西,好像是个经典题。

其实,可以发现,如果 \(n\ge 2\) 的话,其实就和 \(n=2\) 一样的。这个感性理解一下,你发现他很对。

我们考虑找到最大的 \(l\)\(r\) 不一样的位 \(id\)。然后考虑,首先 \([l,2^{id}-1)\) 这些数字,肯定可以取到是吧。

然后考虑大于等于 \(2^{id}\) 的数字能取那些?

我们就可以设 \(l'\) 表示将 \(l\)\(id\) 位改成 \(1\) 的。很明显,如果我们找到 \(r\)\(id\) 之后的第一个 \(1\),那么,这个一后面的后缀一串 \(1\) 都可以取到设为 \(r1\)

然后,对于如果我们选了 \([l,2^{id})\) 里的数字,如果 \(l+2^{id}<r\) 的后缀,那么就可以都取到,否则应该有可以取到 \(r1-l+1\) 个数字。

地理考试炸了

题目背景

EAM:
你的重心放计算机上就ok

风影剑醉:
[嗯嗯]

EAM:
[吃]

题目

题目描述

定义一棵无根树是三度树,当且仅当它的非叶节点的度数都是三。可以发现,具有\(n\)个叶子节点的一棵三度树恰好有 \(2n-3\) 条边。

考虑四个叶子\(A, B, C, D\),他们能构成三度树 \(T\) 的“四元划分”\(AB|CD\)当且仅当存在一条边,使得删去该边后可以分成两棵子树\(T_1, T_2\)满足\(A \in T_1, B \in T_1, C \in T_2, D \in T_2\) 。注意这里的定义是无序的,也就是说\(AB|CD, BA|CD, DC|AB\)视为是相同的;而 \(AB|CD\)\(AC|BD\) 不相同。令\(Q(T)\)表示\(T\)的所有四元划分的集合。

给定两棵具有\(n\)个叶子节点的三度树\(T_A,T_B\),叶子节点标号均为\(1\)\(n\),非叶节点标号均为\(n+1\)\(2n-2\)。请求出\(|Q(T_A) \Delta Q(T_B)|\) 。在这里, \(A \Delta B\) 表示集合 \(A\) 和集合 \(B\) 的对称差。

输入格式

第一行一个正整数\(n\),表示叶结点数量。

接下来\(2n-3\)行,每行两个正整数\(x,y\),表示第一棵树的一条边。

再接下来\(2n-3\)行,每行两个正整数\(x,y\),表示第二棵树的一条边。

输出格式

一行一个整数,表示答案。

样例输入

5
1 7
2 6
3 6
4 8
5 8
6 7
7 8
1 6
2 7
3 6
4 8
5 8
6 7
7 8

样例输出
4

数据范围

对于\(10\%\)的数据, \(n \leq 20\)

对于\(20\%\)的数据, \(n \leq 100\)

对于\(40\%\)的数据, \(n \leq 300\)

对于\(100\%\)的数据,\(4 \leq n \leq 1000\)

时间限制:2s

空间限制:512MB

AC代码
#include <bits/stdc++.h>
using std::cin;
using std::cout;
using std::vector;
using std::copy;
using std::reverse;
using std::swap;
using std::array;
using std::cerr;
using std::function;
using std::map;
using std::set;
using std::pair;
using std::mt19937;
using std::make_pair;
using std::tuple;
using std::make_tuple;
using std::uniform_int_distribution;
namespace qwq {
	mt19937 eng;
	void init(int Seed) {
		eng.seed(Seed);
		return;
	}
	int rnd(int l = 1, int r = 1000000000) {
		return uniform_int_distribution<int> (l, r)(eng);
	}
}
template <typename T>
inline T min(const T &x, const T &y) {
	return x < y ? x : y;
}
template<typename T>
inline T max(const T &x, const T &y) {
	return x > y ? x : y;
}
template <typename T>
inline T read() {
	T x = 0;
	bool f = 0;
	char ch = getchar();
	while (!isdigit(ch)) {
		f = ch == '-';
		ch = getchar();
	}
	while (isdigit(ch)) {
		x = x * 10 + ch - '0';
		ch = getchar();
	}
	return f ? -x : x;
}
#define O(x) cerr << #x << " : " << x << '\n'
const double Pi = acos(-1);
const int MAXN = 262144, MOD = 998244353, inv2 = (MOD + 1) / 2;
auto Ksm = [] (int x, int y) -> int {
	if (y < 0) {
		y %= MOD - 1;
		y += MOD - 1;
	}
	int ret = 1;
	for (; y; y /= 2, x = (long long) x * x % MOD) {
		if (y & 1) {
			ret = (long long) ret * x % MOD;
		} 
	}
	return ret;
};
auto Mod = [] (int x) -> int {
	if (x >= MOD) {
		return x - MOD;
	}
	else if (x < 0) {
		return x + MOD;
	}
	else {
		return x;
	}
};
inline int ls(int k) {
	return k << 1;
}
inline int rs(int k) {
	return k << 1 | 1;
}
int N, vis[MAXN];
long long ans;
// 组合数
long long C(int x, int y) {
	long long ret = 1;
	for (int i = x - y + 1; i <= x; ++i) {
		ret *= i;
	}
	for (int i = 1; i <= y; ++i) {
		ret /= i;
	}
	return ret;
}
// 学到了一种结构体的写法,很妙
struct Graph {
	vector<int> e[MAXN];
	void add_edge(int x, int y) {
		e[x].push_back(y);
		e[y].push_back(x);
	}
};
// 两棵树
struct Tree2 : Graph {
	int sz[MAXN][4];
	void getsz(int u, int fa) {
		sz[u][1] = sz[u][2] = sz[u][3] = 0;
		if (u <= N && vis[u]) {
			sz[u][vis[u]] = 1;
		}
		for (auto &i: e[u]) {
			if (i != fa) {
				getsz(i, u);
				for (int j = 1; j <= 3; ++j) {
					sz[u][j] += sz[i][j];
				}
			}
		}
		return;
	}
	long long getans(int u, int fa) {
		long long ret = 0, sum = 0;
		for (auto &i: e[u]) {
			for (auto &j: e[u]) {
				if (i < j) {
					for (int k = 1; k <= 3; ++k) {
						for (int l = 1; l <= 3; ++l) {
							if (k != l) {
								ret += C((i != fa) ? sz[i][k] : (sz[N + 1][k] - sz[u][k]), 2) * C((j != fa) ? sz[j][l] : (sz[N + 1][l] - sz[u][l]), 2);
							}
						}
					}
				}
			}
		}
		if (fa) {
			// 这条边
			for (int i = 1; i <= 3; ++i) {
				for (int j = 1; j <= 3; ++j) {
					if (i != j) {
						ret -= C(sz[u][i], 2) * C(sz[N + 1][j] - sz[u][j], 2);
					}
				}
			}
		}
		for (auto &i: e[u]) {
			if (i != fa) {
				ret += getans(i, u);
			}
		}
		return ret;
	}
} T2;
struct Tree1 : Graph {
	void draw(int u, int fa, int col) {
		vis[u] = col;
		for (auto &i: e[u]) {
			if (i != fa) {
				draw(i, u, col);
			}
		}
		return;
	}
	void getans(int u, int fa) {
		int cnt = 0;
		// O(ans);
		memset(vis, 0, sizeof vis);
		for (auto &i: e[u]) {
			draw(i, u, ++cnt);
		}
		T2.getsz(N + 1, 0);
		ans += T2.getans(N + 1, 0);
		// O(ans);
		if (fa) {
			memset(vis, 0, sizeof vis);
			draw(u, fa, 1);
			draw(fa, u, 2);
			T2.getsz(N + 1, 0);
			ans -= T2.getans(N + 1, 0);
		}
		for (auto &i: e[u]) {
			if (i != fa) {
				getans(i, u);
			}
		}
		return;
	}
} T1;
int main() {
	freopen("b.in", "r", stdin);
	freopen("b.out", "w", stdout);
	std::ios::sync_with_stdio(0);
	cout << std::fixed << std::setprecision(8);
	cin.tie(0);
	cout.tie(0);
	qwq::init(20050112);
	cin >> N;
	for (int i = 1, x, y; i < 2 * N - 2; ++i) {
		cin >> x >> y;
		T1.add_edge(x, y);
	}
	for (int i = 1, x, y; i < 2 * N - 2; ++i) {
		cin >> x >> y;
		T2.add_edge(x, y);
	}
	T1.getans(N + 1, 0);
	ans = 2 * C(N, 4) - 2 * ans;
	cout << ans << '\n';
	// cout << (-3 / 2);
	return (0-0);
}

就是很牛逼的,我们可以考虑这个对称差就是两个全集减去这个什么傻逼交集。

全集自然就是 \(\binom {N}{4}\) 因为我们选取四个顶点,肯定有一种合法方案(因为度数小于 4)。

然后考虑交集怎么求。

很难搞。

我们是不是可以这么搞,对于一棵树来说,一个四元划分 \(Q(AB|CD)\) 合法,那么肯定是说,\(AB\)\(CD\) 这条链没有交,那么考虑他们之间的那条链。肯定满足边-点=1。于是可以考虑经典的容斥,每次考虑去掉一个边还是点。然后这样就可以求一个树时候的答案。

我们考虑怎么做两个树的交集?我们首先枚举第一棵树去掉的边或者点。这样这些点被划分成了几个连通块。然后对于二树上的每种连通块的点的容斥可以单独做。

这样就做完了。

posted @ 2022-03-13 00:09  siriehn_nx  阅读(64)  评论(0编辑  收藏  举报