前情提要:
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 是长度在 444 和 101010 之间的字符串,包含小写字母和
?
。 - 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 个用户,编号从 111 到 NNN ;有 MMM 个竞赛页面,编号从 111 到 MMM 。最初,没有用户拥有查看任何竞赛页面的权限。
你会收到 QQQ 个查询,需要按顺序处理。每个查询都属于以下三种类型之一:
1 X Y
:授予用户 XXX 查看竞赛页面 YYY 的权限。2 X
:授予用户 XXX 查看所有比赛页面的权限。3 X Y
:回答用户 XXX 是否可以查看比赛页面 YYY 。
一个用户有可能多次被授予查看同一个比赛页面的权限。
限制因素
- 1≤N≤2×1051 \le N \le 2\times 10^51≤N≤2×105
- 1≤M≤2×1051 \le M \le 2\times 10^51≤M≤2×105
- 1≤Q≤2×1051 \le Q \le 2\times 10^51≤Q≤2×105
- 1≤X≤N1 \le X \le N1≤X≤N
- 1≤Y≤M1 \le Y \le M1≤Y≤M
- 所有输入值均为整数。
第一个操作和第三个查询都是可以使用 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(1≤i,j≤∣B∣) 都是 ∣Bi−Bj∣≠D|B_i - B_j|\neq D∣Bi−Bj∣=D。
求最少需要删除的元素个数。
限制因素
- 1≤N≤2×1051 \le N \le 2\times 10^51≤N≤2×105
- 0≤D≤1060 \le D \le 10^60≤D≤106
- 0≤Ai≤1060 \le A_i \le 10^60≤Ai≤106
- 所有输入值均为整数。
首先特判 D=0D=0D=0 的情况,显然就是每一种数出现次数然后 −1-1−1 的和。
然后就是 D≠0D \not = 0D=0 的情况。首先把 AAA 所有元素去重,把每一种数去重前的出现次数记录下来。
然后就是熟悉的图论建模方式:对于 AiA_iAi,如果 Ai+DA_i + DAi+D 出现在了 AAA 里面,则连 Ai→Ai+DA_i \to A_i+DAi→Ai+D 的一条边。 代表这两个东西不能同时存在。
显然最终会形成一条链(边的数量可能是 000),而且这条链上面的所有边的两端至少都有一种数要被赶尽杀绝(不妨设每一个点的点权都是这个数在原数组种出现的次数)。
但是这个时候不能单纯的黑白染色,可以在链上跑线性 dpdpdp。fif_ifi 表示选了第 iii 个,则显然有 fi=min(fi−1,fi−2)+valif_i = \min(f_{i-1},f_{i-2})+val_ifi=min(fi−1,fi−2)+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
问题陈述
有两个字符串多集合,分别是 XXX 和 YYY ,它们最初都是空的。
给你 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 是介于 111 和 2×1052 \times 10^52×105 之间的整数,包括首尾两个整数。
- Ti∈{1,2}T_i \in \{1,2\}Ti∈{1,2}
- 每个 SiS_iSi 都是长度介于 111 和 5×1055\times 10^55×105 之间的字符串,包含小写英文字母。
- ∑i=1Q∣Si∣≤5×105\displaystyle \sum_{i=1}^Q |S_i| \leq 5 \times 10^5i=1∑Q∣Si∣≤5×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)(i−1) \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^51≤Q≤3×105
- 0≤yi≤1090 \le y_i \le 10^90≤yi≤109
- 1≤xi≤1091 \le x_i \le 10^91≤xi≤109
- 所有输入值均为整数。
动态开点权值线段树板子。
这道题和第一题有一点异曲同工之妙。
看到这种题,果断想到使用线段树来维护区间的奇数位和。因为值域有亿点点大,所以考虑动态开点权值线段树。
显然合并的话还是需要维护区间的偶数位和,然后再记录一下每一个区间里面出现了多少个数即可。
#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;
}