前情提要:
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 1≤N≤2×105
- 1 ≤ M ≤ 2 × 1 0 5 1 \le M \le 2\times 10^5 1≤M≤2×105
- 1 ≤ Q ≤ 2 × 1 0 5 1 \le Q \le 2\times 10^5 1≤Q≤2×105
- 1 ≤ X ≤ N 1 \le X \le N 1≤X≤N
- 1 ≤ Y ≤ M 1 \le Y \le M 1≤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
问题陈述
给你一个长度为 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(1≤i,j≤∣B∣) 都是 ∣ B i − B j ∣ ≠ D |B_i - B_j|\neq D ∣Bi−Bj∣=D。
求最少需要删除的元素个数。
限制因素
- 1 ≤ N ≤ 2 × 1 0 5 1 \le N \le 2\times 10^5 1≤N≤2×105
- 0 ≤ D ≤ 1 0 6 0 \le D \le 10^6 0≤D≤106
- 0 ≤ A i ≤ 1 0 6 0 \le A_i \le 10^6 0≤Ai≤106
- 所有输入值均为整数。
首先特判 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 Ai→Ai+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(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
问题陈述
有两个字符串多集合,分别是 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=1∑Q∣Si∣≤5×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) (i−1) \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 1≤Q≤3×105
- 0 ≤ y i ≤ 1 0 9 0 \le y_i \le 10^9 0≤yi≤109
- 1 ≤ x i ≤ 1 0 9 1 \le x_i \le 10^9 1≤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;
}