A.Scoreboard(循环)
题意:
两个队伍进行 N N N场比赛。在第 i i i场比赛中 ( 1 ≤ i ≤ N ) (1≤i≤N) (1≤i≤N),两队各得到 X i X_i Xi和 Y i Y_i Yi分。比赛结束后总分更高的一方获胜。输出胜利的队伍。如果两支队伍总分相同,则为平局。
分析:
按题意模拟,进行累加然后比较总分大小即可。
代码:
#include <bits/stdc++.h>using namespace std;
typedef long long LL;
const LL mod = 998244353;
const LL N = 5e5;int main() {int n;cin >> n;int sum1, sum2;sum1 = sum2 = 0;for (int i = 0; i < n; i++) {int x, y;cin >> x >> y;sum1 += x;sum2 += y;}if (sum1 > sum2) {cout << "Takahashi" << endl;} else if (sum1 < sum2) {cout << "Aoki" << endl;} else {cout << "Draw" << endl;}return 0;
}
B.Extended ABC(模拟)
题意:
定义扩展 A A A字符串、扩展 B B B字符串、扩展 C C C字符串和扩展 A B C ABC ABC字符串如下:
- 如果 S S S中的所有字符都是 A A A,则 S S S是一个扩展 A A A字符串。
- 如果 S S S中的所有字符都是 B B B,则 S S S是一个扩展 B B B字符串。
- 如果 S S S中的所有字符都是 C C C,则 S S S是一个扩展 C C C字符串。
- 如果存在一个扩展 A A A字符串 S A S_A SA,一个扩展 B B B字符串 S B S_B SB和一个扩展 C C C字符串 S C S_C SC,使得按照顺序连接 S A S_A SA、 S B S_B SB和 S C S_C SC所得到的串等于 S S S,则称 S S S为一个扩展 A B C ABC ABC字符串。
例如, A B C ABC ABC、 A A A和 A A A B B B C C C C C C C AAABBBCCCCCCC AAABBBCCCCCCC都是扩展 A B C ABC ABC字符串,但 A B B A A A C ABBAAAC ABBAAAC和 B B B C C C C C C C A A A BBBCCCCCCCAAA BBBCCCCCCCAAA不是。
注意,空串既可以看作是一个扩展 A A A串,也可以看作是一个扩展 B B B串或者扩展 C C C串。给定由字母 A A A、 B B B和 C C C组成的一个字符串 S S S 。若 S S S是一段扩展 A B C ABC ABC字符串,输出 Y e s Yes Yes;否则,输出 N o No No。
分析:
题意为给定一个字符串,判断是否形为按顺序出现数个 A A A,数个 B B B,数个 C C C(个数可以为 0 0 0)。可以使用 u n i q u e unique unique函数去重后判断是否符合要求的顺序,也可以循环遍历字符串判断。
代码:
#include <bits/stdc++.h>using namespace std;
typedef long long LL;
const LL mod = 998244353;
const LL N = 5e5;int main() {string S;cin >> S;S.erase(unique(S.begin(), S.end()), S.end());if (S == "ABC" || S == "A" || S == "B" || S == "C" || S == "AB" || S == "AC" || S == "BC") {cout << "Yes" << endl;} else {cout << "No" << endl;}return 0;
}
C.Lining Up 2(模拟)
题意:
有 N N N个人站在一条线上:编号为 1 − N 1-N 1−N。人们的排列方式是一个长度为 N N N的序列 A = ( A 1 , A 2 , . . . , A N ) A=(A_1,A_2,...,A_N) A=(A1,A2,...,AN)。 A i A_i Ai ( 1 ≤ i ≤ N ) (1≤i≤N) (1≤i≤N),表示以下信息:如果 A i = − 1 A_i=-1 Ai=−1,则第 i i i个人在队伍最前面;如果 A i ≠ − 1 A_i≠-1 Ai=−1,则第 i i i个人紧随着第 A i A_i Ai个人后面。按照从前到后的顺序输出队伍中每个人的编号。
分析:
n n n个人站成一排,给定每个人位于某人的右边,求这个排列。
对序列 A A A进行映射,令 r [ i ] = j r[i]=j r[i]=j,表示第 j j j个人在第 i i i个人的右边。找到最左边的人 l l l,即值为 − 1 -1 −1的人,不断更新 l = r [ l ] l=r[l] l=r[l]并输出即可。本题也可以使用链表进行操作,找到最右侧的人,然后向左依次输出。
代码:
#include <bits/stdc++.h>using namespace std;
typedef long long LL;
const LL mod = 998244353;
const LL N = 5e5;
int n, cnt, tmp;
map<int, int> pos;int main() {cin >> n;for (int i = 0; i < n; i++) {cin >> cnt;pos[cnt] = i + 1;}tmp = pos[-1];cout << tmp;for (int i = 0; i < n - 1; i++) {tmp = pos[tmp];cout << " " << tmp;}cout << endl;return 0;
}
D.Cheating Gomoku Narabe(前缀和)
题意:
有一个 H H H行 W W W列的网格。用 ( i , j ) (i,j) (i,j)表示从上到下第 i i i行、从左到右第 j j j列的单元格。每个单元格包含字符 ′ o ′ 'o' ′o′、 ′ x ′ 'x' ′x′和 ′ . ′ '.' ′.′。单元格中的字符由长度为 W W W的 H H H个字符串 S 1 , S 2 , . . . , S H S_1,S_2,...,S_H S1,S2,...,SH来表示;单元格 ( i , j ) (i,j) (i,j)中的字符是字符串 S i S_i Si的第 j j j个字符。
对于这个网格,可以重复以下操作任意次数(可能为零):
- 选择一个带有 ′ . ′ '.' ′.′字符的单元格,并将该单元格中的字符更改为 ′ o ′ 'o' ′o′。
判断是否可能通过操作使得网格中存在 K K K个水平或垂直的连续为 ′ o ′ 'o' ′o′的单元格(即满足以下两种条件之一)。如果可能,输出实现此目标所需的最小操作数。
- 存在整数对 ( i , j ) (i,j) (i,j),满足 1 ≤ i ≤ H 1≤i≤H 1≤i≤H和 1 ≤ j ≤ W − K + 1 1≤j≤W−K+1 1≤j≤W−K+1,使得在单元格 ( i , j ) , ( i , j + 1 ) , … , ( i , j + K − 1 ) (i,j),(i,j+1),…,(i,j+K−1) (i,j),(i,j+1),…,(i,j+K−1)中的字符都是 ′ o ′ 'o' ′o′。
- 存在整数对 ( i , j ) (i,j) (i,j),满足 1 ≤ i ≤ H − K + 1 1≤i≤H−K+1 1≤i≤H−K+1和 1 ≤ j ≤ W 1≤j≤W 1≤j≤W,使得在单元格 ( i , j ) , ( i + 1 , j ) , … , ( i + K − 1 , j ) (i,j),(i+1,j),…,(i+K−1,j) (i,j),(i+1,j),…,(i+K−1,j)中的字符都是 ′ o ′ 'o' ′o′。
分析:
本题可以暴力枚举,对每一行,每一列都判断一次,取最小代价输出。枚举这连续 k k k个格子的左端点 ,然后往右的 k k k个格子里,不能有 ′ x ′ 'x' ′x′,然后对 ′ . ′ '.' ′.′的数量取最小值。需要预处理 ′ x ′ 'x' ′x′和 ′ . ′ '.' ′.′数量的前缀和,也可以使用滑动窗口维护字符数量。
代码:
#include <bits/stdc++.h>using namespace std;
typedef long long LL;
const LL mod = 998244353;
const LL N = 5e5;
const int MAXN = 1e9 + 5;
int h, w, k;int sa(vector<string> s) {int ans = MAXN;for (auto &tmp: s) {vector<int> preo(tmp.size(), 0), prex(tmp.size(), 0);preo[0] = tmp[0] == 'o';prex[0] = tmp[0] == 'x';for (int j = 1; j < tmp.size(); ++j) {preo[j] = preo[j - 1] + (tmp[j] == 'o');prex[j] = prex[j - 1] + (tmp[j] == 'x');}for (int j = 0; j + k - 1 < tmp.size(); ++j) {if (prex[j + k - 1] - (j ? prex[j - 1] : 0) == 0) {ans = min(ans, k - (preo[j + k - 1] - (j ? preo[j - 1] : 0)));}}}return ans;
}void solve() {cin >> h >> w >> k;vector<string> s(h);vector<string> t(w);for (auto &tmp: s)cin >> tmp;for (int i = 0; i < w; i++) {for (int j = 0; j < h; j++) {t[i] += s[j][i];}}int ans = min(sa(s), sa(t));if (ans == MAXN)cout << "-1" << endl;elsecout << ans << endl;
}int main() {solve();return 0;
}
E.Bad Juice(交互、二进制枚举)
题意:
有 N N N瓶果汁,编号从 1 1 1到 N N N。其中恰好有一瓶变质了。即使喝一小口变质的果汁,第二天也会拉肚子。高桥必须在第二天之前确定出哪瓶果汁变质了。为此,他决定打电话给尽可能少的朋友,并将其中的 N N N瓶果汁分给他们。他可以给每个朋友任意数量的瓶子,并且每瓶果汁都可以分给任意个朋友。输出要打电话的朋友数以及如何分配果汁,然后接收关于每个朋友是否在第二天拉肚子的信息,并输出变质果汁瓶子的编号。
分析:
因为每位朋友是否拉肚子这个信息只有 1 1 1和 0 0 0两种情况,可以先将饮料的编号转化成二进制,需要朋友的数量就是 N N N在二进制中的位数。
对于朋友 i i i,让他喝在二进制中,编号第 i i i位为 1 1 1的饮料。如果第 i i i位朋友拉了肚子,就可以知道变质的饮料第 i i i位为 1 1 1。
考虑如果 N N N是二的整数次幂,那么饮料 N N N会且仅会被第 M M M位朋友喝,第 M M M位朋友喝且仅喝饮料 N N N。在这种情况下,我们就可以少叫一位朋友,并且让饮料 N N N不被任何人喝。
如果所有朋友都不拉肚子,就可以判断饮料 N N N变质了。
代码:
#include <bits/stdc++.h>using namespace std;
typedef long long LL;
const LL mod = 998244353;
const LL N = 1005;
int n, m;
vector<int> a[N];int hibit(int x) {int ret = 1;int t = x & (-x);while (x != t) {x -= t;t = x & (-x);}for (int i = 1;; i <<= 1) {if (i == x)return ret;ret++;}
}int lowbits(int x) {return x & (-x);
}int lowbit(int x) {int t = x & (-x);if (t == 1)return 1;for (int i = 1;; i++)if ((t >> i) == 0)return i;
}int main() {cin >> n;m = hibit(n);if (lowbits(n) == n)m--;cout << m << endl;for (int i = 1; i <= n - (lowbits(n) == n); i++) {int t = i;while (t) {a[lowbit(t)].push_back(i);t -= lowbits(t);}}for (int i = 1; i <= m; i++) {cout << a[i].size() << " ";int l = a[i].size();for (int j = 0; j < l; j++)cout << a[i][j] << " ";cout << endl;}string s;cin >> s;s = " " + s;if (lowbits(n) == n) {bool flag = 1;for (int i = 1; i < s.size(); i++)if (s[i] == '1') {flag = 0;break;}if (flag) {cout << n << endl;return 0;}}int ans = 0;for (int i = 1; i <= m; i++) {if (s[i] == '1')ans += (1 << i) / 2;}cout << ans << endl;return 0;
}
F.Usual Color Ball Problems(双指针、思维)
题意:
给定正整数 N N N、 M M M、 K K K和长度为 N N N的正整数序列 ( C 1 , C 2 , … , C N ) (C_1,C_2,…,C_N) (C1,C2,…,CN)。对于每个 r = 0 , 1 , 2 , … , N − 1 r=0,1,2,…,N−1 r=0,1,2,…,N−1,输出以下问题的答案:
有一个由 N N N个彩色球组成的序列。编号为 i = 1 , 2 , … , N i=1,2,…,N i=1,2,…,N,从序列开头开始第 i i i个球的颜色是 C i C_i Ci。此外,还有 M M M个编号为 1 1 1到 M M M的空盒子。在执行以下操作后确定盒子中球的总数目。
- 首先,进行以下操作 r r r次:将序列中最前面的球移到序列末尾。
- 然后,重复以下操作直至序列中至少还剩一个球:如果存在一个盒子,其中装有至少一个但不多于 K K K个与序列最前面的球相同颜色的球,则将最前面的球放入该盒子。如果没有这样的盒子:则若存在一个空箱子,将最前面的球放入编号最小的那个空箱子。如果没有空箱子,则拿走最前面的球而不把它放进任何箱子。
分析:
首先执行 r r r次,循环询问,对于每个开头的长为 n n n的序列都询问一次,将序列扩成二倍。
考虑双指针,使用双指针维护第一个不在盒子内的球的位置,手动模拟一下可以发现它是单调递增。并且,一旦确定了不在盒子内的球的位置,设为 x x x,那么前面的盒子内的每种颜色球的个数可以被确定,同时这些球占的盒子的个数也可被确定。位置在 x x x位置之后,但因为和 x x x位置之前放入盒子里的球同色,而能被继续放入盒子的球,也可以被确定。
假设某种颜色总共有 a a a个球,当前占了 b b b个盒子,则这种颜色的球总共放了 m i n ( b ∗ k , a ) min(b*k,a) min(b∗k,a)个。一旦确定了不在盒子内的球的位置 x x x,前面在盒子里的同色的球的个数也能确定。记颜色 c c c的球在盒子里的球为 n u m [ c ] num[c] num[c]个,则占了 ⌈ n u m [ c ] k ⌉ \lceil \frac{num[c]}{k} \rceil ⌈knum[c]⌉个盒子。双指针维护第一个不在盒子里的球的位置即可。
代码:
#include <bits/stdc++.h>using namespace std;
typedef long long LL;
const LL mod = 998244353;
const LL N = 2e5 + 10;
int n, m, k, cnt[N], c[2 * N], col, ans, num[N];void add(int c, int v) {int x = (num[c] + k - 1) / k;col -= x;ans -= min(x * k, cnt[c]);num[c] += v;x = (num[c] + k - 1) / k;col += x;ans += min(x * k, cnt[c]);
}int main() {cin >> n >> m >> k;for (int i = 1; i <= n; i++) {cin >> c[i];c[n + i] = c[i];cnt[c[i]]++;}int r = 0;for (int l = 1; l <= n; l++) {while (r + 1 <= l + n - 1 && col < m) {add(c[++r], 1);}cout << ans << endl;add(c[l], -1);}return 0;
}
G.Tree Inversion
题意:
给定一棵具有 N N N个顶点的树 T T T,顶点编号为顶点 1 1 1,顶点 2 2 2,…,顶点 N N N。第 i i i条边 ( 1 ≤ i < N ) (1 \le i < N) (1≤i<N)连接顶点 u i u_i ui和 v i v_i vi。
对于树 T T T中的一个顶点 u u u,定义 f ( u ) f(u) f(u)如下: f ( u ) f(u) f(u)表示满足以下两个条件的包含顶点 v v v和 w w w的顶点对的对数:
- 顶点 w w w在连接 u u u和 v v v的路径中;
- v < w v < w v<w。
当 u = w u=w u=w或 v = w v=w v=w时,顶点 w w w也包含在连接顶点 u u u和 v v v的路径中。
计算 f ( 1 ) f(1) f(1), f ( 2 ) f(2) f(2),…, f ( N ) f(N) f(N)的值。
分析:
首先,我们考虑组 ( v , w ) ( v < w ) (v, w)(v < w) (v,w)(v<w) 对于哪个 u u u做出贡献。由于 v < w v < w v<w,满足的条件是在 u , v u,v u,v路径上存在 w w w。这可以重新表述为“在去除了 w w w的森林中, v v v属于的连通分量不包含 u u u”。考虑以任意顶点为根节点的有根树,则满足该条件的 u u u集合可以用部分子树或者除了某个特定部分子树之外全部的形式表示。固定部分子树,并考虑每种形式对应的组 ( v , w ) (v,w) (v,w)所做出的贡献。
-
考虑以部分子树顶点 p p p及其后代构成的部分子树。满足 ( v , w ) (v,w) (v,w)对应贡献给顶点 u u u与该部分子树相同的 ( v , w ) ( v < w ) (v,w)(v < w) (v,w)(v<w),需要满足以下条件: w = p w=p w=p且 v v v不包含在该部分子树中。这样的组 ( v , w ) (v,w) (v,w)数量可通过计算KaTeX parse error: Unexpected end of input in a macro argument, expected '}' at end of input: …(\text{该部分子树中小于wKaTeX parse error: Expected 'EOF', got '}' at position 5: 的顶点数}̲x)得到。
-
考虑除了特定某个部分子树之外全部顶点 p p p及其后代构成的部分子树。满足 ( v , w ) (v,w) (v,w)对应贡献给顶点 u u u与该部分子树以外全部相同的 ( v , w ) ( v < w ) (v,w)(v < w) (v,w)(v<w),需要满足以下条件: w w w是 p p p的父节点且 v v v包含在该部分子树中。这样的组 ( v , w ) (v,w) (v,w)数量可通过计算(该部分子树中小于 w w w的顶点数 x x x)得到。
综上所述,如果能对于每个部分子树和整数 q q q进行快速求解,(该部分子树中小于 q q q的顶点数 x x x),就可以计算出各自的贡献。相当于 D F S DFS DFS遍历顺序并在平面上进行查询。按 D F S DFS DFS遍历顺序进行扫描,可以归结为列的前缀和。可以使用二进制索引或线段树进行高效查询。
代码:
#include <bits/stdc++.h>using namespace std;
typedef long long LL;
const LL mod = 998244353;
const LL N = 2e5 + 10;
const int MAXN = 2e5 + 7;int n, rt[MAXN], ls[MAXN * 20], rs[MAXN * 20], num[MAXN * 20], cnt;void pushup(int x) {num[x] = num[ls[x]] + num[rs[x]];
}void update(int &x, int l, int r, int pos, int val = 1) {if (!x)x = ++cnt;if (l == r) {num[x] += val;return;}int mid = (l + r) >> 1;if (pos <= mid)update(ls[x], l, mid, pos, val);elseupdate(rs[x], mid + 1, r, pos, val);pushup(x);
}int merge(int x1, int x2, int l, int r) {if ((!x1) || (!x2))return x1 + x2;if (l == r) {num[x1] += num[x2];return x1;}int mid = (l + r) >> 1;ls[x1] = merge(ls[x1], ls[x2], l, mid);rs[x1] = merge(rs[x1], rs[x2], mid + 1, r);pushup(x1);return x1;
}int query(int x, int l, int r, int L, int R) {if (!x)return 0;if (L <= l && r <= R)return num[x];int mid = (l + r) >> 1, ans = 0;if (L <= mid)ans += query(ls[x], l, mid, L, R);if (R > mid)ans += query(rs[x], mid + 1, r, L, R);return ans;
}int main() {cin >> n;vector<vector<int>> edge(n);for (int i = 0; i < n - 1; ++i) {int u, v;cin >> u >> v;--u, --v;edge[u].push_back(v);edge[v].push_back(u);}LL tot = 0;vector<LL> p(n, 0);auto DFS = [&](auto self, int u, int fa) -> void {for (auto &v: edge[u]) {if (v == fa)continue;self(self, v, u);LL tmp = query(rt[v], 1, n, 1, u + 1);tot += tmp;p[v] -= tmp;rt[u] = merge(rt[u], rt[v], 1, n);}update(rt[u], 1, n, u + 1);p[u] += u + 1 - query(rt[u], 1, n, 1, u + 1);};DFS(DFS, 0, 0);vector<LL> ans(n, 0);auto solve = [&](auto self, int u, int fa, LL pre) -> void {ans[u] += tot + p[u] + pre;for (auto &v: edge[u]) {if (v == fa)continue;self(self, v, u, pre + p[u]);}};solve(solve, 0, 0, 0);for (auto &i: ans)cout << i << " ";cout << endl;return 0;
}
学习交流
以下为学习交流QQ群,群号: 546235402,每周题解完成后都会转发到群中,大家可以加群一起交流做题思路,分享做题技巧,欢迎大家的加入。