比赛链接:

https://vjudge.net/contest/506331

A - Static Query on Tree

题意:

给定一棵 \(n\) 个节点的树,每个节点只能向根节点走,\(q\) 次询问,每次给出三个集合 \(A, B, C\),分别从 \(A, B, C\) 中取出三个元素 \(x, y, z\),问 \(x -> z, y -> z\) 两条路径都经过的点有几个。

思路:

将路径的方向反一下,即 1 为根,将 \(A\)\(B\) 中的元素向根的路径进行染色,将 \(C\) 的子树所有的节点进行染色,同时含有三种颜色的就是答案。先进行树链剖分,接下来就是区间操作了。
三个集合对树的节点的染色操作通过位或上 1,2,4 来完成,这样子当答案为 7 的时候就是三种颜色都染上了。
为了加快查询的速度,每个节点再存上位与的答案,若答案为 7,那么整个区间都是符合条件的,就不用再往下走了。同时,若位或是 < 7 的,也不用查询,因为它的子节点肯定缺少某种颜色了。
每次查询重复建树,会超时,所以再对节点加入一个标记,如果标记为 1,那么后面会对子节点进行清零,然后再进行相应的操作。

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int n, q;
struct node{
	int l, r, tag, v1, v2, clear;
}tr[N << 2];
void pushup(int u){
	tr[u].v1 = tr[u << 1].v1 & tr[u << 1 | 1].v1;
	tr[u].v2 = tr[u << 1].v2 | tr[u << 1 | 1].v2;
}
void pushdown(int u){
	auto &root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
	if (root.clear){
		left.clear = right.clear = 1;
		left.v1 = right.v1 = 0;
		left.v2 = right.v2 = 0;
		left.tag = right.tag = 0;
		root.clear = 0;
	}
	left.v1 |= root.tag;
	left.v2 |= root.tag;
	left.tag |= root.tag;
	right.v1 |= root.tag;
	right.v2 |= root.tag;
	right.tag |= root.tag;
	root.tag = 0;
}
void build(int u, int l, int r){
	tr[u] = {l, r};
	if (l == r) return;
	int mid = l + r >> 1;
	build(u << 1, l, mid);
	build(u << 1 | 1, mid + 1, r);
}
void update(int u, int l, int r, int k){
	if (tr[u].l >= l && tr[u].r <= r){
		tr[u].v1 |= k;
		tr[u].v2 |= k;
		tr[u].tag |= k;
	}
	else {
		pushdown(u);
		int mid = tr[u].l + tr[u].r >> 1;
		if (l <= mid) update(u << 1, l, r, k);
		if (r > mid) update(u << 1 | 1, l, r, k);
		pushup(u);
	}
}
int query(int u, int l, int r){
	if (tr[u].l >= l && tr[u].r <= r){
		if (tr[u].v1 >= 7) return tr[u].r - tr[u].l + 1;
		if (tr[u].l == tr[u].r) return 0;
	}
	pushdown(u);
	int mid = tr[u].l + tr[u].r >> 1, sum = 0;
	if (l <= mid && tr[u << 1].v2 >= 7) sum = query(u << 1, l, r);
	if (r > mid && tr[u << 1 | 1].v2 >= 7) sum += query(u << 1 | 1, l, r);
	return sum;
}
int top[N], son[N], dep[N], parent[N], sz[N], id[N];
vector < vector<int> > e(N);
int cnt = 0;
void dfs1(int u){  //计算子树大小、深度、父亲和重儿子
	sz[u] = 1;
	dep[u] = dep[parent[u]] + 1;
	for (auto v : e[u]){
		if (v == parent[u]) continue;
		parent[v] = u;
		dfs1(v);
		sz[u] += sz[v];
		if (!son[u] || sz[son[u]] < sz[v]){
			son[u] = v;
		}
	}
}
void dfs2(int u, int up){  //计算链的头部节点
	id[u] = ++ cnt;
	top[u] = up;
	if (son[u]) dfs2(son[u], up);
	for (auto v : e[u]){
		if (v == parent[u] || v == son[u]) continue;
		dfs2(v, v);
	}
}
void init(int s){
	dfs1(s);
	dfs2(s, s);
}
void updateRoot(int u, int v, int k){
	while(top[u] != top[v]){
		if (dep[top[u]] < dep[top[v]]) swap(u, v);
		update(1, id[top[u]], id[u], k);
		u = parent[top[u]];
	}
	if (dep[u] > dep[v]) swap(u, v);
	update(1, id[u], id[v], k);
}
void updateSon(int u, int pos){
	update(1, id[u], id[u] + sz[u] - 1, pos);
}
void Clear(){
	for (int i = 1; i <= n; i ++ )
		e[i].clear();
	cnt = 0;
}
void solve(){
	cin >> n >> q;
	for (int i = 1; i < n; i ++ ){
		int r;
		cin >> r;
		e[r].push_back(i + 1);
	}
	init(1);
	build(1, 1, n);
	while(q -- ){
		int A, B, C;
		cin >> A >> B >> C;
		for (int i = 0; i < A; i ++ ){
			int u;
			cin >> u;
			updateRoot(1, u, 1);
		}
		for (int i = 0; i < B; i ++ ){
			int u;
			cin >> u;
			updateRoot(1, u, 2);
		}
		for (int i = 0; i < C; i ++ ){
			int u;
			cin >> u;
			updateSon(u, 4);
		}
		cout << query(1, 1, n) << "\n";
		tr[1].clear = 1;
		tr[1].v1 = tr[1].v2 = tr[1].tag = 0;
	}
	Clear();
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);
	int T = 1;
	cin >> T;
	while(T -- ){
		solve();
	}
	return 0;
}

B - C++ to Python

题意:

C++ 语言转为 python。

思路:

简单的签到,直接输出删除了下划线和字母的字符串。

代码:

#include <bits/stdc++.h>
using namespace std;
#define LL long long
void solve(){
	string s;
	cin >> s;
	for (int i = 0; i < (LL)s.size(); i ++ )
		if (s[i] == '(' || s[i] == ')' || s[i] == '-' || (s[i] >= '0' && s[i] <= '9') || s[i] == ',')
			cout << s[i];
	cout << "\n";
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);
	LL T = 1;
	cin >> T;
	while(T -- ){
		solve();
	}
	return 0;
}

C - Copy

题意:

给定一个长为 \(n\) 的序列,\(q\) 次操作,操作分两种:
1.将 \([l, r]\) 区间中的数字复制后插入到第 \(r\) 位后面。
2.查询第 \(k\) 位的数字。
最后输出每次查询结果的异或和。

思路:

如果按照给定的顺序操作,每次需要考虑当前这个修改操作对后面产生的影响,会将整个查询区间分成很多份。
所以反过来考虑,将操作储存,离线操作。
当当前这位是修改操作,那么它会让它修改位置后面的所有查询操作每一位都减去 \([r - l + 1]\)
同时,因为求的是异或和,所以没必要每次都将值记录下来,只用知道某一位的数字用了几次,若偶数次,那么它对答案其实没有产生影响。因此,采用 \(bitset\) 去优化操作。

代码:

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 1e5 + 10;
LL a[N], op[N], l[N], r[N];
bitset <N> f, low, high;
void solve(){
	LL n, q;
	cin >> n >> q;
	for (int i = 1; i <= n; i ++ )
		cin >> a[i];
	for (int i = 1; i <= q; i ++ ){
		cin >> op[i] >> l[i];
		if (op[i] == 1) cin >> r[i];
	}
	f.reset();
	for (int i = q; i >= 1; i -- ){
		LL a = l[i], b = r[i];
		if (op[i] == 1){
			low = f & (~bitset<N>(0) >> (N - b - 1));  //将修改位置前面的位取出
			high = f & (~bitset<N>(0) << (b + 1));  //将修改位置之后的位置取出
			f = low ^ (high >> (b - a + 1));  //后面的位置向前面移动
		}
		else{
			f[a] = f[a] ^ 1;
		}
	}
	LL ans = 0;
	for (int i = 1; i <= n; i ++ )
		if (f[i])
			ans ^= a[i];
	cout << ans << "\n";
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);
	LL T = 1;
	cin >> T;
	while(T -- ){
		solve();
	}
	return 0;
}

G - Snatch Groceries

题意:

\(n\) 段区间 \([l, r](l < r)\),表示不同的信号段,按照时间顺序依次发出,当某段区间与之前有重复,重复的信号就不会发射,且停止发射后面的所有信号,问有多少段信号成功发射。

思路:

排序,然后按照题意计算就行。

代码:

#include <bits/stdc++.h>
using namespace std;
#define LL long long
void solve(){
	LL n;
	cin >> n;
	vector < pair<LL, LL> > range(n);
	for (int i = 0; i < n; i ++ )
		cin >> range[i].first >> range[i].second;
	sort(range.begin(), range.end());
	LL ans = 1;
	for (int i = 1; i < n; i ++ ){
		if (range[i].first <= range[i - 1].second){
			ans -- ;
			break;
		}
		ans ++ ;
	}
	cout << ans << "\n";
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);
	LL T = 1;
	cin >> T;
	while(T -- ){
		solve();
	}
	return 0;
}

I - ShuanQ

题意:

已知 \(Q = P^{-1}\)\(P * Q \equiv 1 mod M\)\(M\) 是一个质数。
以及两个等式:
\(E = R * P mod M\)
\(R = E * Q mod M\)
现在知道了 \(P\)\(Q\),且 \(P,Q,E < M\),求 \(R\)

思路:

根据已知条件,容易知道 \(M\)\(P * Q - 1\) 的一个质因数,让 \(P * Q - 1 = k * M\),那么 \(k * M\)\(P * Q - 1\) 的最大质因数。
所以可以求出 \(M\),然后判断是不是满足题目中的条件就可以了。

代码:

#include <bits/stdc++.h>
using namespace std;
#define LL long long
void solve(){
	LL p, q, e;
	cin >> p >> q >> e;
	LL km = p * q - 1, m = -1;
	for (LL i = 2; i <= km / i; i ++ ){
		if (km % i == 0){
			while(km % i == 0){
				km /= i;
			}
			m = i;
		}
	}
	if (km > 1) m = km;
	if (m == -1 || p >= m || q >= m) cout << "shuanQ\n";
	else cout << e * q % m << "\n";
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);
	LL T = 1;
	cin >> T;
	while(T -- ){
		solve();
	}
	return 0;
}

K - DOS Card

题意:

给定长为 \(n\) 的序列,选择一个\(i, j(i < j\),可以得到 \((a_i + a_j) * (a_i - a_j)\) 的分数,\(m\) 次询问,每次询问一个区间 \(L, R\),问从这个区间中选择两对(四个下标要两两不相同),最多能得到多少分。

思路:

\((a_i + a_j) * (a_i - a_j)\)
= \(a_i^2 - a_j^2\)
因为选择了两对,所以答案由四个值组成,有两种情况。
++--, +-+-
将它们拆分开
++-, +--, +-+, -+-
再拆
++, +-, --, -+
再拆
+, -
所以通过线段树维护这十二个变量即可。

代码:

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 2e5 + 10;
const LL INF = 1e18;
LL w[N];
struct Segt{
	struct node{
		LL l, r;
		LL ans1, ans2;	//++-- +-+-
		LL v1, v2, v3, v4;	//++- +-- +-+ -+-
		LL v5, v6, v7, v8;	//++ +- -- -+
		LL a, b;	//+ -
	}tr[N << 2];
	void pushup(node &root, node left, node right){
		root.ans1 = max({left.ans1, right.ans1, left.v1 + right.b, left.a + right.v2, left.v5 + right.v7});
		root.ans2 = max({left.ans2, right.ans2, left.v3 + right.b, left.a + right.v4, left.v6 + right.v6});
		root.v1 = max({left.v1, right.v1, left.v5 + right.b, left.a + right.v6});
		root.v2 = max({left.v2, right.v2, left.v6 + right.b, left.a + right.v7});
		root.v3 = max({left.v3, right.v3, left.v6 + right.a, left.a + right.v8});
		root.v4 = max({left.v4, right.v4, left.v8 + right.b, left.b + right.v6});
		root.v5 = max({left.v5, right.v5, left.a + right.a});
		root.v6 = max({left.v6, right.v6, left.a + right.b});
		root.v7 = max({left.v7, right.v7, left.b + right.b});
		root.v8 = max({left.v8, right.v8, left.b + right.a});
		root.a = max(left.a, right.a);
		root.b = max(left.b, right.b);
	}
	void build(LL u, LL l, LL r){
		tr[u] = {l, r,
		-INF, -INF,
		-INF, -INF, -INF, -INF,
		-INF, -INF, -INF, -INF,
		-INF, -INF};
		if (l == r){
			tr[u].a = w[l];
			tr[u].b = -w[l];
			return;
		}
		LL mid = l + r >> 1;
		build(u << 1, l, mid);
		build(u << 1 | 1, mid + 1, r);
		pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
	}
	node query(LL u, LL l, LL r){
		if (tr[u].l >= l && tr[u].r <= r) return tr[u];
		LL mid = tr[u].l + tr[u].r >> 1;
		if (r <= mid) return query(u << 1, l, r);
		else if (l > mid) return query(u << 1 | 1, l, r);
		else{
			node t = {0, 0,
			-INF, -INF,
			-INF, -INF, -INF, -INF,
			-INF, -INF, -INF, -INF,
			-INF, -INF};
			pushup(t, query(u << 1, l, r), query(u << 1 | 1, l, r));
			return t;
		}
	}
}segt;
void solve(){
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; i ++ ){
		cin >> w[i];
		w[i] *= w[i];
	}
	segt.build(1, 1, n);
	while(m -- ){
		int L, R;
		cin >> L >> R;
		auto t = segt.query(1, L, R);
		cout << max(t.ans1, t.ans2) << "\n";
	}
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);
	int T;
	cin >> T;
	while(T -- ){
		solve();
	}
	return 0;
}

L - Luxury cruise ship

题意:

物品价格为 \(n\),现在有三种货币,分别为 7,31,365,问最少花几个货币可以刚好买物品。

思路:

先预处理一个范围中的价格最少花多少货币,然后范围外的用 365 去买。

代码:

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 1e7 + 10;
LL dp[N];
void solve(){
	LL n;
	cin >> n;
	LL k = 0;
	if (n > N) k = (n - N) / 365 + 1;
	n -= k * 365;
	if (dp[n] > 1e18) cout << "-1\n";
	else cout << k + dp[n] << "\n";
}
void init(){
	memset(dp, 0x3f, sizeof dp);
	dp[7] = 1, dp[31] = 1, dp[365] = 1;
	for (int j = 7; j < N; j ++ ){
		if (~dp[j]){
			if (j + 7 < N) dp[j + 7] = min(dp[j] + 1, dp[j + 7]);
			if (j + 31 < N) dp[j + 31] = min(dp[j] + 1, dp[j + 31]);
			if (j + 365 < N) dp[j + 365] = min(dp[j] + 1, dp[j + 365]);
		}
	}
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);
	init();
	LL T = 1;
	cin >> T;
	while(T -- ){
		solve();
	}
	return 0;
}
posted on 2022-07-25 10:30  Hamine  阅读(138)  评论(0编辑  收藏  举报