本文主要是介绍AtCoder Beginner Contest 349 A~F,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
A. Zero Sum Game(思维)
题意
有 N N N个人一起参加游戏,开始时每个人均拥有 0 0 0点分数。每轮游戏只会在两个人之间进行,胜者将得到 1 1 1分,败者将失去 1 1 1分。已知结束游戏后前 N − 1 N - 1 N−1个人的分数 A 1 , A 2 , … , A N − 1 A_1, A_2, \ldots, A_{N - 1} A1,A2,…,AN−1,问,最后一个人的分数是多少?
分析
既然每轮游戏将会产生两个分数 1 1 1和 − 1 -1 −1,那么对于 N N N个人来说,总分总是固定为 0 0 0的,因此想要知道 A N A_N AN,可以使用总分减去每个人的分数,即 A N = 0 − ∑ i = 1 N − 1 A i A_N = 0 - \sum\limits_{i = 1}^{N - 1}A_i AN=0−i=1∑N−1Ai。
代码
#include<bits/stdc++.h>
using namespace std;void solve() {int n;cin >> n;int sum = 0;for (int i = 2; i <= n; i++) {int a;cin >> a;sum += a;}cout << -sum << endl;
}int main() {solve();return 0;
}
B. Commencement(桶数组)
题意
给出一个仅包含小写字母的字符串 S S S,定义一个字符串是一个好字符串需要对于每一个大于等于 1 1 1的 i i i均满足以下要求:
- 出现次数为 i i i次的字母只可以有 0 0 0个或 2 2 2个。
问:给出的字符串是否为好字符串?
分析
可以使用 c n t cnt cnt数组记录每个字母的出现次数,然后再使用桶数组或 m a p map map记录每个出现次数的字母个数。
最后检查一遍,是否每个出现次数 i i i对应的字母个数,如果均为 0 0 0个或 2 2 2个,那么就输出Yes
,否则,输出No
。
代码
#include<bits/stdc++.h>
using namespace std;int cnt[130];
vector<int> V[105];void solve() {string s;cin >> s;for (auto i : s) {cnt[i - 'a']++;}for (int i = 0; i <= 25; i++) {if (cnt[i]) {V[cnt[i]].push_back(i);}}int n = s.size();for (int i = 1; i <= n; i++) {if (V[i].size() != 0 && V[i].size() != 2) {cout << "No" << endl;return;}}cout << "Yes" << endl;
}int main() {solve();return 0;
}
C.Airport Code(思维)
题意
Airport Code
:如果从字符串 S S S中按以下两种方法获得一个新的字符串,那么这个新获得的字符串就叫做Airport Code
:
-
- 从字符串 S S S中取出一个长为 3 3 3的子序列,并把这个子序列中所有字母变成大写字母。
-
- 从字符串 S S S中取出一个长为 2 2 2的子序列,并把这个子序列中所有字母变成大写字母,然后在这个子序列后加上一个大写字母 X X X。
给出两个字符串 S S S和 T T T,问字符串 T T T是不是字符串 S S S的Airport Code
。
分析
对两个字符串进行字符串匹配(注意大小写),然后根据匹配上的数量进行判断:
-
- 三个字母都匹配上了,必然成立
-
- 匹配上前两个字母,且最后一个未被匹配的字母为 X X X,也成立
-
- 其他情况均不成立
代码
#include<bits/stdc++.h>
using namespace std;void solve() {string s, t;cin >> s >> t;int len_t = t.size();int maxn = 2;if (t[2] == 'X') maxn--;int j = 0;for (auto i : s) {if (j <= maxn && i - 32 == t[j]) {j++;}}if (j > maxn) cout << "Yes" << endl;else cout << "No" << endl;
}int main() {solve();return 0;
}
D.Divide Interval(贪心)
题意
对于两个非负整数 l , r ( l < r ) l, r(l < r) l,r(l<r),定义序列 S ( l , r ) = l , l + 1 , … , r − 1 S(l, r) = l, l + 1, \ldots, r - 1 S(l,r)=l,l+1,…,r−1。而一个序列为好的序列需要满足 S ( l , r ) S(l, r) S(l,r)中的 l = 2 i j , r = 2 i ( j + 1 ) l = 2^{i}j, r = 2^{i}(j + 1) l=2ij,r=2i(j+1)。
给出两个非负整数 L , R ( L < R ) L, R(L < R) L,R(L<R),问,最少使用多少个好的序列 S ( l i , r i ) S(l_i, r_i) S(li,ri),才能组成 L ∼ R − 1 L \sim R - 1 L∼R−1之间的所有数字。
分析
由于好的序列的 l = 2 i j , r − l = 2 i l = 2^{i}j, r - l = 2^{i} l=2ij,r−l=2i,那么为了序列数量尽可能少,取的 2 i 2^{i} 2i就要尽可能大,而 l = 2 i j l = 2^{i}j l=2ij,则 l l l必须为 2 i 2^{i} 2i的倍数,因此,可以对 i i i从大到小进行枚举,当 l l l是 2 i 2^{i} 2i倍数时,就可以继续检查 l + 2 i l + 2^{i} l+2i后是否会超过右边界 R R R,如果也没有超过右边界,那么就表示当前序列能当前能选的所有序列中最长的一个,将当前序列的起点 l l l和终点 l + 2 i l + 2^{i} l+2i记录到vector
中,并将起点修改为 l + 2 i l + 2^{i} l+2i继续寻找下一个序列。
Tips
- 2 i 2^{i} 2i可以通过位运算 1 < < i 1 << i 1<<i快速得到
- 由于本题数据范围较大 R ≤ 2 60 R \le 2^{60} R≤260,而C++程序中写下的数字 1 1 1默认是
int
类型,那么直接使用 1 < < i 1 << i 1<<i会因为数据溢出导致程序出现错误,需要使用 1 l l 1ll 1ll代替数字 1 1 1,写作 1 l l < < i 1ll << i 1ll<<i。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;vector<pair<ll, ll> > ans;void solve() {ll l, r;cin >> l >> r;while (l < r) {for (ll i = 60; i >= 0; i--) {if (l % (1ll << i) == 0 && (l / (1ll << i) + 1) * (1ll << i) <= r) {ans.push_back({l, (l / (1ll << i) + 1) * (1ll << i)});l = (l / (1ll << i) + 1) * (1ll << i);break;}}}cout << ans.size() << endl;for (auto i : ans) {cout << i.first << ' ' << i.second << endl;}
}int main() {solve();return 0;
}
E.Weighted Tic-Tac-Toe(博弈)
题意
给出一个 3 × 3 3 \times 3 3×3的网格,每个网格中包含着一个数字,其中第 i i i行第 j j j列的数字为 A i , j A_{i, j} Ai,j,保证 ∑ i = 1 3 ∑ j = 1 3 A i , j \sum\limits_{i = 1}^{3}\sum\limits_{j = 1}^{3}A_{i, j} i=1∑3j=1∑3Ai,j是个奇数。
开始时,网格上每个位置上均为空着的的,高桥和Aoki
将在这个网格上玩井字棋。
如果谁赢下了这场井字棋,谁就是胜者。
如果出现平局,则看两名选手放置棋子的方格数字之和,谁的数字之和更大,那么谁就赢下了这局游戏。
高桥为先手,假设两人均采取最优策略,问谁将会赢下这场游戏。
分析
考虑贪心,发现局部最优解无法推出全局最优解,当前最优的策略可能会将自己推入必败点,因此不能使用贪心思维来解决本题。
考虑进行博弈,使用dfs
枚举操作,看两名选手操作后能否将对手推入必败点,如果可以,那么当前状态就是必胜点,而如果没有任何办法使对手进入必败点,那么说明自己当前状态已经是必败点了。
使用 d f s ( i ) dfs(i) dfs(i)表示 i i i操作后的胜负情况, d f s ( i ) = 1 dfs(i) = 1 dfs(i)=1表示先手获胜, d f s ( i ) = 0 dfs(i) = 0 dfs(i)=0表示后手获胜。
每轮搜索时,由于都可能出现已经决出胜负的情况,因此,在搜索开头就需要检查胜负情况,当出现以下情况时,说明已经决出胜负了:
-
- 某一行某一列某一对角线已经出现放置了连续的三个同色棋子
-
- 网格中已经被棋子放满了
对于情况1,根据放置的棋子颜色返回本局游戏的结果
对于情况2,检查比赛双方的数字之和,数字之和较大的选手获胜
而如果情况1,2均为出现,则继续搜索并放置棋子。
hint
- 本题数据范围较大,需要使用
long long
类型存储数据
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;ll a[5][5], vis[5][5], score[5];int check() {for (int i = 1; i <= 3; i++) {if (vis[i][1] != 0 && vis[i][1] == vis[i][2] && vis[i][2] == vis[i][3]) return vis[i][1];if (vis[1][i] != 0 && vis[1][i] == vis[2][i] && vis[2][i] == vis[3][i]) return vis[1][i];}if (vis[1][1] != 0 && vis[1][1] == vis[2][2] && vis[2][2] == vis[3][3]) return vis[1][1];if (vis[1][3] != 0 && vis[1][3] == vis[2][2] && vis[2][2] == vis[3][1]) return vis[1][3];for (int i = 1; i <= 3; i++) {for (int j = 1; j <= 3; j++) {if (vis[i][j] == 0) return 0;}}return 3;
}int dfs(int op) {int ans = check();if (ans == 3) return score[1] > score[2];//平局,由分数else if (ans != 0) return ans % 2;for (int i = 1; i <= 3; i++) {for (int j = 1; j <= 3; j++) {if (vis[i][j] == 0) {score[op] += a[i][j];vis[i][j] = op;int res = dfs(op == 1 ? 2 : 1);score[op] -= a[i][j];vis[i][j] = 0;if (op % 2 == res) {//返回的结果也需根据当前选手来进行判断return res;}}}}return op == 1 ? 0 : 1;//op为1,则表示先手没有必胜策略,先手必败//反之,后手必败
}void solve() {for (int i = 1; i <= 3; i++) {for (int j = 1; j <= 3; j++) {cin >> a[i][j];}}int ans = dfs(1);if (ans == 1) cout << "Takahashi" << endl;else cout << "Aoki" << endl;
}int main() {solve();return 0;
}
F.Subsequence LCM(DP)
题意
给出一个包含 N N N个数字的数组 A = A 1 , A 2 , … , A N A = A_1, A_2, \ldots, A_N A=A1,A2,…,AN,以及一个正整数 M M M,问,在数组 A A A中有多少个子序列中,所有数字的最小公倍数恰好为 M M M。
分析
由唯一分解定理可知,任何一个合数均可被分解成若干个素数的幂次相乘的形式,而 M ≤ 1 0 16 M \le 10^{16} M≤1016,而前14个素数的乘积约等于 1.3 e 16 > 1 0 16 1.3e16 > 10^{16} 1.3e16>1016,因此, M M M最多只可能被分成 13 13 13个素数乘积的形式。
然后考虑哪些因素会影响到最小公倍数,不难想到,对于 M = p 1 x 1 × p 2 x 2 … M = p_1^{x_{1}} \times p_2^{x_{2}} \ldots M=p1x1×p2x2…,每个 p i x j p_{i}^{x_{j}} pixj,均取自包含子数组中包含 p i p_{i} pi这个因子的最高次项,那么,如果想要组成数字 M M M,就需要保证子数字对于 p i x j p_{i}^{x_{j}} pixj,均能至少取出其中一个数字包含该因子,同时该数字包含素因子 p i p_{i} pi的次数不能超过 x j x_{j} xj。
因此,可以将输入的数字转化为仅保留该数字包含数字 M M M中 p i x j p_{i}^{x_{j}} pixj这些因子的形式,即如果包含因子 p i p_i pi,但次数低于 x j x_{j} xj,就将该因子去掉,因为该因子不会对最小公倍数产生贡献,然后将剩余的数字存储到数组中,即使用 c n t [ i ] cnt[i] cnt[i]表示对答案产生 i i i贡献的数字个数。
如果包含了其他因子,或因子次数过高,那么必然不能属于答案的一部分,可以将该数字直接舍去。
然后考虑初始方案,不难想到,对于 c n t [ 0 ] cnt[0] cnt[0],这些数字不会对答案产生贡献,但也不会影响到答案,因此,任取这些数字均能产生一种不同的答案,那么这些数字对于每组方案均能产生 2 c n t [ 0 ] 2^{cnt[0]} 2cnt[0]的贡献。
使用二进制枚举对组成数字 M M M的素因子进行枚举,并使用背包算法记录方案数量(当二进制枚举枚举到的数字有 n u m num num个,不难想到,只要结果中包含至少一个该数字,就能保证结果包含这些数字对应的因子,那么取这些数字的方案总数为 2 n u m − 1 2^{num} - 1 2num−1,减去的1为全部数字均不取的方案数)。
如 M M M的素因子有 T T T种,那么最后的答案即为 d p [ ( 1 < < T ) − 1 ] dp[(1 << T) - 1] dp[(1<<T)−1],其中 ( 1 < < T ) − 1 (1 << T) - 1 (1<<T)−1为长度为 T T T的全 1 1 1二进制串,表示包含了 M M M的所有的素因子。
坑点
当 M = 1 M = 1 M=1时,会多计算一个答案,需要将答案减去。
代码
#include <bits/stdc++.h>using namespace std;
typedef long long ll;ll n, m, a[200005], mod = 998244353;
ll p[20], fact[20], pw[200005], cnt[200005], dp[200005];int main() {cin >> n >> m;int id = 0;ll num = m;for (ll i = 2; i * i <= num; i++) {if (num % i == 0) {p[id] = i;fact[id] = 1;while (num % i == 0) {fact[id] *= i;num /= i;}id++;}}if (num != 1) {p[id] = num;fact[id++] = num;}for (int i = 1; i <= n; i++) {cin >> a[i];ll val = 0;//记录a[i]对答案的贡献for (int j = 0; j < id; j++) {if (a[i] % fact[j] == 0) {a[i] /= fact[j];if (a[i] % p[j] == 0) break;val |= (1 << j);}while (a[i] % p[j] == 0) {a[i] /= p[j];}}if (a[i] == 1) {cnt[val]++;}}pw[0] = 1;for (int i = 1; i <= n; i++) {pw[i] = pw[i - 1] * 2 % mod;}dp[0] = pw[cnt[0]];for (int i = 1; i < (1 << id); i++) {for (int j = (1 << id) - 1; j >= 0; j--) {dp[i | j] = (dp[i | j] + dp[j] * (pw[cnt[i]] - 1)) % mod;}}if (!id) dp[(1 << id) - 1]--;cout << dp[(1 << id) - 1] << endl;return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。
这篇关于AtCoder Beginner Contest 349 A~F的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!