Atcoder Beginner Contest 403 A to E、G 题解

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

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


A

问题陈述

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

AAA 的奇数索引元素之和。即求出 A1+A3+A5+⋯+AmA_1 + A_3 + A_5 + \dots + A_mA1+A3+A5++Am ,其中 mmm 是不超过 NNN 的最大奇数。


直接使用 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

问题陈述

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

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

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

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

可能是最无脑的做法?

考虑直接枚举 TTT 四个 ? 填入的字母以得到 SSS,共 26426^4264 种情况。然后再暴力找子串是否有 UUU 即可。复杂度 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 上有 NNN 个用户,编号从 111NNN ;有 MMM 个竞赛页面,编号从 111MMM 。最初,没有用户拥有查看任何竞赛页面的权限。

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

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

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

限制因素
  • 1≤N≤2×1051 \le N \le 2\times 10^51N2×105
  • 1≤M≤2×1051 \le M \le 2\times 10^51M2×105
  • 1≤Q≤2×1051 \le Q \le 2\times 10^51Q2×105
  • 1≤X≤N1 \le X \le N1XN
  • 1≤Y≤M1 \le Y \le M1YM
  • 所有输入值均为整数。

第一个操作和第三个查询都是可以使用 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

问题陈述

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

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

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

限制因素
  • 1≤N≤2×1051 \le N \le 2\times 10^51N2×105
  • 0≤D≤1060 \le D \le 10^60D106
  • 0≤Ai≤1060 \le A_i \le 10^60Ai106
  • 所有输入值均为整数。

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

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

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

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

但是这个时候不能单纯的黑白染色,可以在链上跑线性 dpdpdpfif_ifi 表示选了第 iii 个,则显然有 fi=min⁡(fi−1,fi−2)+valif_i = \min(f_{i-1},f_{i-2})+val_ifi=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

问题陈述

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

给你 QQQ 个查询,让你按顺序处理。在第 iii 个查询中,你会收到一个整数 TiT_iTi 和一个字符串 SiS_iSi 。如果是 Ti=1T_i=1Ti=1 ,将 SiS_iSi 插入 XXX ;如果是 Ti=2T_i=2Ti=2 ,将 SiS_iSi 插入 YYY

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

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

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

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

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

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

即可。

#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

问题陈述

有一个初始为空的序列 AAA

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

您将得到一个整数 yiy_iyi 。如果是 i=1i=1i=1 ,让 zzz 成为 000 ;否则,让 zzz 成为 (i−1)(i-1)(i1) \th查询的答案。定义 xi=((yi+z) mod 109)+1x_i=((y_i+z)\bmod 10^9)+1xi=((yi+z)mod109)+1 。将 xix_ixi 追加到 AAA 的末尾。

然后,设 B=(B1,B2,…,Bi)B=(B_1,B_2,\ldots,B_i)B=(B1,B2,,Bi) 是按升序排序的序列 AAA ,求 BBB 中奇数索引元素的和。即求出 B1+B3+B5+⋯+BmB_1 + B_3 + B_5 + \dots + B_mB1+B3+B5++Bm ,其中 mmm 是不超过 iii 的最大奇数。

限制因素
  • 1≤Q≤3×1051 \le Q \le 3\times 10^51Q3×105
  • 0≤yi≤1090 \le y_i \le 10^90yi109
  • 1≤xi≤1091 \le x_i \le 10^91xi109
  • 所有输入值均为整数。

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

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

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

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


#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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值