Atcoder Beginner Contest 403 A to E、G 题解

前情提要:在这里插入图片描述
AC ABCDEG,首次拿下金名表现分。如果 F 能及时调出来那就更好了。

如果你下面的思路没有看懂,看代码也没有关系。


A

问题陈述

给你一个长度为 N N N 的正整数序列: A = ( A 1 , A 2 , … , A N ) A=(A_1,A_2,\dots,A_N) A=(A1,A2,,AN) .

A A A 的奇数索引元素之和。即求出 A 1 + A 3 + A 5 + ⋯ + A m A_1 + A_3 + A_5 + \dots + A_m A1+A3+A5++Am ,其中 m m m 是不超过 N N N 的最大奇数。


直接使用 for 循环即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int a[N];
int n;

int main() {
	cin >> n;
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	int ans = 0;
	for (int i = 1; i <= n; i += 2)
		ans += a[i];
	cout << ans << endl;
	return 0;
}

B

问题陈述

给你一个由小写英文字母和?组成的字符串 T T T 和一个由小写英文字母组成的字符串 U U U

字符串 T T T 是由某个只有小写字母的字符串 S S S ,把里面的恰好四个 ? 替换成小写字母得到的。

判断原始字符串 S S S 是否可能包含作为连续子串的 U U U

限制因素
  • T T T 是长度在 4 4 4 10 10 10 之间的字符串,包含小写字母和 ?
  • T T T 包含恰好四次出现的 ?
  • U U U 是长度在 1 1 1 ∣ T ∣ |T| T 之间的字符串,包含小写字母。

可能是最无脑的做法?

考虑直接枚举 T T T 四个 ? 填入的字母以得到 S S S,共 2 6 4 26^4 264 种情况。然后再暴力找子串是否有 U U U 即可。复杂度 O ( 能过 ) O(能过) O(能过)

#include <bits/stdc++.h>
using namespace std;
string t, u;
int pos[4];

int main() {
	cin >> t >> u;
	int nw = 0;
	for (int i = 0; i < (int)t.size(); i++)
		if (t[i] == '?')
			pos[nw++] = i;
	for (char a = 'a'; a <= 'z'; a++)
		for (char b = 'a'; b <= 'z'; b++)
			for (char c = 'a'; c <= 'z'; c++)
				for (char d = 'a'; d <= 'z'; d++) {
					t[pos[0]] = a, t[pos[1]] = b, t[pos[2]] = c, t[pos[3]] = d;
					for (int i = 0; i + u.size() - 1 < (int)t.size(); i++) {//找子串
						if (t.substr(i, (int)u.size()) == u) {
							cout << "Yes\n";
							return 0;
						}
					}
				}
	cout << "No\n";
	return 0;
}

C

问题陈述

WAtCoder 上有 N N N 个用户,编号从 1 1 1 N N N ;有 M M M 个竞赛页面,编号从 1 1 1 M M M 。最初,没有用户拥有查看任何竞赛页面的权限。

你会收到 Q Q Q 个查询,需要按顺序处理。每个查询都属于以下三种类型之一:

  • 1 X Y:授予用户 X X X 查看竞赛页面 Y Y Y 的权限。
  • 2 X:授予用户 X X X 查看所有比赛页面的权限。
  • 3 X Y:回答用户 X X X 是否可以查看比赛页面 Y Y Y

一个用户有可能多次被授予查看同一个比赛页面的权限。

限制因素
  • 1 ≤ N ≤ 2 × 1 0 5 1 \le N \le 2\times 10^5 1N2×105
  • 1 ≤ M ≤ 2 × 1 0 5 1 \le M \le 2\times 10^5 1M2×105
  • 1 ≤ Q ≤ 2 × 1 0 5 1 \le Q \le 2\times 10^5 1Q2×105
  • 1 ≤ X ≤ N 1 \le X \le N 1XN
  • 1 ≤ Y ≤ M 1 \le Y \le M 1YM
  • 所有输入值均为整数。

第一个操作和第三个查询都是可以使用 pair 和 set 解决掉的,而第二个查询直接记下来即可。因为操作中没有删除权限的操作,所以这样是可以的。

#include <bits/stdc++.h>
using namespace std;
set<pair<int, int> > st;
const int N = 200010;
bool f[N];

int main() {
	int n, m, q;
	cin >> n >> m >> q;
	while (q--) {
		int op;
		cin >> op;
		if (op == 1) {
			int x, y;
			cin >> x >> y;
			st.insert({x, y});
		} else if (op == 2) {
			int x;
			cin >> x;
			f[x] = 1;
		} else {
			int x, y;
			cin >> x >> y;
			if (f[x] || st.find({x, y}) != st.end()) cout << "Yes\n";
			else
				cout << "No\n";
		}
	}
	return 0;
}

D

问题陈述

给你一个长度为 N N N 的整数序列 A = ( A 1 , A 2 , … , A N ) A=(A_1,A_2,\dots,A_N) A=(A1,A2,,AN) 和一个非负整数 D D D 。我们希望从 A A A 中删除尽可能少的元素,得到满足以下条件的序列 B B B

  • 所有 i , j    ( 1 ≤ i , j ≤ ∣ B ∣ ) i,j \; (1 \le i , j \le |B|) i,j(1i,jB) 都是 ∣ B i − B j ∣ ≠ D |B_i - B_j|\neq D BiBj=D

求最少需要删除的元素个数。

限制因素
  • 1 ≤ N ≤ 2 × 1 0 5 1 \le N \le 2\times 10^5 1N2×105
  • 0 ≤ D ≤ 1 0 6 0 \le D \le 10^6 0D106
  • 0 ≤ A i ≤ 1 0 6 0 \le A_i \le 10^6 0Ai106
  • 所有输入值均为整数。

首先特判 D = 0 D=0 D=0 的情况,显然就是每一种数出现次数然后 − 1 -1 1 的和。

然后就是 D ≠ 0 D \not = 0 D=0 的情况。首先把 A A A 所有元素去重,把每一种数去重前的出现次数记录下来。

然后就是熟悉的图论建模方式:对于 A i A_i Ai,如果 A i + D A_i + D Ai+D 出现在了 A A A 里面,则连 A i → A i + D A_i \to A_i+D AiAi+D 的一条边。 代表这两个东西不能同时存在。

显然最终会形成一条链(边的数量可能是 0 0 0),而且这条链上面的所有边的两端至少都有一种数要被赶尽杀绝(不妨设每一个点的点权都是这个数在原数组种出现的次数)。

但是这个时候不能单纯的黑白染色,可以在链上跑线性 d p dp dp f i f_i fi 表示选了第 i i i 个,则显然有 f i = min ⁡ ( f i − 1 , f i − 2 ) + v a l i f_i = \min(f_{i-1},f_{i-2})+val_i fi=min(fi1,fi2)+vali。复杂度为 O ( N + V ) O(N+V) O(N+V),可以通过。

#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, d;
const int N = 500010;
int a[N];
int cnt[N * 5];
int to[N];
map<int, int> mp;
bool f[N];
vector<int> v, dp;

signed main() {
	cin >> n >> d;
	for (int i = 1; i <= n; i++)
		cin >> a[i], cnt[a[i]]++;
	sort(a + 1, a + n + 1);
	n = unique(a + 1, a + n + 1) - a - 1;
	for (int i = 1; i <= n; i++)
		mp[a[i]] = i;
	if (d == 0) {
		int ans = 0;
		for (int i = 1; i <= n; i++)
			ans += cnt[a[i]] - 1;
		cout << ans << endl;
		return 0;
	}
	for (int i = 1; i <= n; i++)
		if (cnt[a[i] + d])
			to[i] = mp[a[i] + d];
	int ans = 0;
	for (int i = 1; i <= n; i++) {
		if (f[i])
			continue;
		v.clear();
		dp.clear();
		dp.push_back(0);
		int x = i;
		while (x != 0)
			v.push_back(cnt[a[x]]), f[x] = 1, x = to[x], dp.push_back(1e15);
		for (int i = 0; i < (int)v.size(); i++) {
			dp[i + 1] = min(dp[i] + v[i], dp[i + 1]);
			if (i)
				dp[i + 1] = min(dp[i - 1] + v[i], dp[i + 1]);
		}
		ans += min(dp[(int)v.size()], dp[(int)v.size() - 1]);
	}
	cout << ans << endl;
	return 0;
}

E

问题陈述

有两个字符串多集合,分别是 X X X Y Y Y ,它们最初都是空的。

给你 Q Q Q 个查询,让你按顺序处理。在第 i i i 个查询中,你会收到一个整数 T i T_i Ti 和一个字符串 S i S_i Si 。如果是 T i = 1 T_i=1 Ti=1 ,将 S i S_i Si 插入 X X X ;如果是 T i = 2 T_i=2 Ti=2 ,将 S i S_i Si 插入 Y Y Y

处理完每个查询后,打印此值:

  • Y Y Y 中没有以 X X X 为前缀的字符串个数。
限制因素
  • Q Q Q 是介于 1 1 1 2 × 1 0 5 2 \times 10^5 2×105 之间的整数,包括首尾两个整数。
  • T i ∈ { 1 , 2 } T_i \in \{1,2\} Ti{1,2}
  • 每个 S i S_i Si 都是长度介于 1 1 1 5 × 1 0 5 5\times 10^5 5×105 之间的字符串,包含小写英文字母。
  • ∑ i = 1 Q ∣ S i ∣ ≤ 5 × 1 0 5 \displaystyle \sum_{i=1}^Q |S_i| \leq 5 \times 10^5 i=1QSi5×105

前面的东西都是比较简单的算法,但是这道题需要前置知识字典树

看到前缀,你想到了什么?没错就是字典树。

在每一个点上面都记录一下 Y Y Y 里面的字符串经过了这个点多少次。

然后还需要记录一下这个点是不是在某一个 X X X 串的末尾。

即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 500010;
int nxt[N][26];
vector<int> v[N];
bool f[N], f2[N];
int ans = 0;
int cnt = 1;

void add(string s, int id) {
	int pos = 1;
	bool x = 1;
	for (auto i : s) {
		if (!nxt[pos][i - 'a'])
			nxt[pos][i - 'a'] = ++cnt;
		pos = nxt[pos][i - 'a'];
		if (f2[pos] == 1)
			x = 0;
		v[pos].push_back(id);
	}
	f[id] = x, ans += x;
}

void get(string s) {
	int pos = 1;
	bool ff = 0;
	for (auto i : s) {
		if (!nxt[pos][i - 'a'])
			nxt[pos][i - 'a'] = ++cnt, ff = 1;
		pos = nxt[pos][i - 'a'];
	}
	f2[pos] = 1;
	if (!ff) {
		for (auto i : v[pos])
			if (f[i])
				ans--, f[i] = 0;
		v[pos].clear();
	}
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int q;
	cin >> q;
	int nw = 0;
	while (q--) {
		int op;
		cin >> op;
		string s;
		cin >> s;
		if (op == 2)
			add(s, ++nw);
		else
			get(s);
		cout << ans << endl;
	}
	return 0;
}

G

问题陈述

有一个初始为空的序列 A A A

给你 Q Q Q 个查询,让你按顺序处理。下面解释一下 i i i -th 查询:

您将得到一个整数 y i y_i yi 。如果是 i = 1 i=1 i=1 ,让 z z z 成为 0 0 0 ;否则,让 z z z 成为 ( i − 1 ) (i-1) (i1) \th查询的答案。定义 x i = ( ( y i + z )   m o d   1 0 9 ) + 1 x_i=((y_i+z)\bmod 10^9)+1 xi=((yi+z)mod109)+1 。将 x i x_i xi 追加到 A A A 的末尾。

然后,设 B = ( B 1 , B 2 , … , B i ) B=(B_1,B_2,\ldots,B_i) B=(B1,B2,,Bi) 是按升序排序的序列 A A A ,求 B B B 中奇数索引元素的和。即求出 B 1 + B 3 + B 5 + ⋯ + B m B_1 + B_3 + B_5 + \dots + B_m B1+B3+B5++Bm ,其中 m m m 是不超过 i i i 的最大奇数。

限制因素
  • 1 ≤ Q ≤ 3 × 1 0 5 1 \le Q \le 3\times 10^5 1Q3×105
  • 0 ≤ y i ≤ 1 0 9 0 \le y_i \le 10^9 0yi109
  • 1 ≤ x i ≤ 1 0 9 1 \le x_i \le 10^9 1xi109
  • 所有输入值均为整数。

动态开点权值线段树板子。

这道题和第一题有一点异曲同工之妙。

看到这种题,果断想到使用线段树来维护区间的奇数位和。因为值域有亿点点大,所以考虑动态开点权值线段树。

显然合并的话还是需要维护区间的偶数位和,然后再记录一下每一个区间里面出现了多少个数即可。


#include <bits/stdc++.h>
#define mid ((l + r) >> 1)
#define int long long
using namespace std;
int q;
const int N = 3e7+10, mod = 1e9;
int ls[N], rs[N];
int cnt = 1;

struct node {
	int sum1, sum2, len; //奇数位和,偶数位和,长度
} seg[N];

node merge(node x, node y) {
	if (x.len == x.sum1 && x.len == x.sum2 && x.len == -1)
		return y;
	if (y.len == y.sum1 && y.len == y.sum2 && y.len == -1)
		return x;
	node ans;
	ans.len = x.len + y.len;
	if (x.len % 2 == 0)
		ans.sum1 = x.sum1 + y.sum1, ans.sum2 = x.sum2 + y.sum2;
	else
		ans.sum1 = x.sum1 + y.sum2, ans.sum2 = x.sum2 + y.sum1;
	return ans;
}

void upd(int u, int l, int r, int pos) {
	if (l == r) {
		if (seg[u].len % 2 == 0)
			seg[u].len++, seg[u].sum1 += pos;
		else
			seg[u].len++, seg[u].sum2 += pos;
		return ;
	}
	if (pos <= mid) {
		if (ls[u] == 0)
			ls[u] = ++cnt;
		upd(ls[u], l, mid, pos);
	} else {
		if (rs[u] == 0)
			rs[u] = ++cnt;
		upd(rs[u], mid + 1, r, pos);
	}
	seg[u] = merge(seg[ls[u]], seg[rs[u]]);
}

signed main() {
	seg[0] = {-1, -1, -1};
	cin >> q;
	int lst = 0;
	while (q--) {
		int x;
		cin >> x;
		x = (x + lst) % mod + 1;
		upd(1, 1, 1e9, x);
		cout << seg[1].sum1 << endl;
		lst = seg[1].sum1;
	}
	return 0;
}

AtCoder Beginner Contest 134 是一场 AtCoder 的入门级比赛,以下是每道题的简要题解: A - Dodecagon 题目描述:已知一个正十二边形的边长,求它的面积。 解题思路:正十二边形的内角为 $150^\circ$,因此可以将正十二边形拆分为 12 个等腰三角形,通过三角形面积公式计算面积即可。 B - Golden Apple 题目描述:有 $N$ 个苹果和 $D$ 个盘子,每个盘子最多可以装下 $2D+1$ 个苹果,求最少需要多少个盘子才能装下所有的苹果。 解题思路:每个盘子最多可以装下 $2D+1$ 个苹果,因此可以将苹果平均分配到每个盘子中,可以得到最少需要 $\lceil \frac{N}{2D+1} \rceil$ 个盘子。 C - Exception Handling 题目描述:给定一个长度为 $N$ 的整数序列 $a$,求除了第 $i$ 个数以外的最大值。 解题思路:可以使用两个变量 $m_1$ 和 $m_2$ 分别记录最大值和次大值。遍历整个序列,当当前数不是第 $i$ 个数时,更新最大值和次大值。因此,最后的结果应该是 $m_1$ 或 $m_2$ 中较小的一个。 D - Preparing Boxes 题目描述:有 $N$ 个盒子和 $M$ 个物品,第 $i$ 个盒子可以放入 $a_i$ 个物品,每个物品只能放在一个盒子中。现在需要将所有的物品放入盒子中,每次操作可以将一个盒子内的物品全部取出并分配到其他盒子中,求最少需要多少次操作才能完成任务。 解题思路:首先可以计算出所有盒子中物品的总数 $S$,然后判断是否存在一个盒子的物品数量大于 $\lceil \frac{S}{2} \rceil$,如果存在,则无法完成任务。否则,可以用贪心的思想,每次从物品数量最多的盒子中取出一个物品,放入物品数量最少的盒子中。因为每次操作都会使得物品数量最多的盒子的物品数量减少,而物品数量最少的盒子的物品数量不变或增加,因此这种贪心策略可以保证最少需要的操作次数最小。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值