A. Zero Sum Game(思维)
题意
有NNN个人一起参加游戏,开始时每个人均拥有000点分数。每轮游戏只会在两个人之间进行,胜者将得到111分,败者将失去111分。已知结束游戏后前N−1N - 1N−1个人的分数A1,A2,…,AN−1A_1, A_2, \ldots, A_{N - 1}A1,A2,…,AN−1,问,最后一个人的分数是多少?
分析
既然每轮游戏将会产生两个分数111和−1-1−1,那么对于NNN个人来说,总分总是固定为000的,因此想要知道ANA_NAN,可以使用总分减去每个人的分数,即AN=0−∑i=1N−1AiA_N = 0 - \sum\limits_{i = 1}^{N - 1}A_iAN=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(桶数组)
题意
给出一个仅包含小写字母的字符串SSS,定义一个字符串是一个好字符串需要对于每一个大于等于111的iii均满足以下要求:
- 出现次数为iii次的字母只可以有000个或222个。
问:给出的字符串是否为好字符串?
分析
可以使用cntcntcnt数组记录每个字母的出现次数,然后再使用桶数组或mapmapmap记录每个出现次数的字母个数。
最后检查一遍,是否每个出现次数iii对应的字母个数,如果均为000个或222个,那么就输出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:如果从字符串SSS中按以下两种方法获得一个新的字符串,那么这个新获得的字符串就叫做Airport Code:
-
- 从字符串SSS中取出一个长为333的子序列,并把这个子序列中所有字母变成大写字母。
-
- 从字符串SSS中取出一个长为222的子序列,并把这个子序列中所有字母变成大写字母,然后在这个子序列后加上一个大写字母XXX。
给出两个字符串SSS和TTT,问字符串TTT是不是字符串SSS的Airport Code。
分析
对两个字符串进行字符串匹配(注意大小写),然后根据匹配上的数量进行判断:
-
- 三个字母都匹配上了,必然成立
-
- 匹配上前两个字母,且最后一个未被匹配的字母为XXX,也成立
-
- 其他情况均不成立
代码
#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−1S(l, r) = l, l + 1, \ldots, r - 1S(l,r)=l,l+1,…,r−1。而一个序列为好的序列需要满足S(l,r)S(l, r)S(l,r)中的l=2ij,r=2i(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(li,ri)S(l_i, r_i)S(li,ri),才能组成L∼R−1L \sim R - 1L∼R−1之间的所有数字。
分析
由于好的序列的l=2ij,r−l=2il = 2^{i}j, r - l = 2^{i}l=2ij,r−l=2i,那么为了序列数量尽可能少,取的2i2^{i}2i就要尽可能大,而l=2ijl = 2^{i}jl=2ij,则lll必须为2i2^{i}2i的倍数,因此,可以对iii从大到小进行枚举,当lll是2i2^{i}2i倍数时,就可以继续检查l+2il + 2^{i}l+2i后是否会超过右边界RRR,如果也没有超过右边界,那么就表示当前序列能当前能选的所有序列中最长的一个,将当前序列的起点lll和终点l+2il + 2^{i}l+2i记录到vector中,并将起点修改为l+2il + 2^{i}l+2i继续寻找下一个序列。
Tips
- 2i2^{i}2i可以通过位运算1<<i1 << i1<<i快速得到
- 由于本题数据范围较大R≤260R \le 2^{60}R≤260,而C++程序中写下的数字111默认是
int类型,那么直接使用1<<i1 << i1<<i会因为数据溢出导致程序出现错误,需要使用1ll1ll1ll代替数字111,写作1ll<<i1ll << i1ll<<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×33 \times 33×3的网格,每个网格中包含着一个数字,其中第iii行第jjj列的数字为Ai,jA_{i, j}Ai,j,保证∑i=13∑j=13Ai,j\sum\limits_{i = 1}^{3}\sum\limits_{j = 1}^{3}A_{i, j}i=1∑3j=1∑3Ai,j是个奇数。
开始时,网格上每个位置上均为空着的的,高桥和Aoki将在这个网格上玩井字棋。
如果谁赢下了这场井字棋,谁就是胜者。
如果出现平局,则看两名选手放置棋子的方格数字之和,谁的数字之和更大,那么谁就赢下了这局游戏。
高桥为先手,假设两人均采取最优策略,问谁将会赢下这场游戏。
分析
考虑贪心,发现局部最优解无法推出全局最优解,当前最优的策略可能会将自己推入必败点,因此不能使用贪心思维来解决本题。
考虑进行博弈,使用dfs枚举操作,看两名选手操作后能否将对手推入必败点,如果可以,那么当前状态就是必胜点,而如果没有任何办法使对手进入必败点,那么说明自己当前状态已经是必败点了。
使用dfs(i)dfs(i)dfs(i)表示iii操作后的胜负情况,dfs(i)=1dfs(i) = 1dfs(i)=1表示先手获胜,dfs(i)=0dfs(i) = 0dfs(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)
题意
给出一个包含NNN个数字的数组A=A1,A2,…,ANA = A_1, A_2, \ldots, A_NA=A1,A2,…,AN,以及一个正整数MMM,问,在数组AAA中有多少个子序列中,所有数字的最小公倍数恰好为MMM。
分析
由唯一分解定理可知,任何一个合数均可被分解成若干个素数的幂次相乘的形式,而M≤1016M \le 10^{16}M≤1016,而前14个素数的乘积约等于1.3e16>10161.3e16 > 10^{16}1.3e16>1016,因此,MMM最多只可能被分成131313个素数乘积的形式。
然后考虑哪些因素会影响到最小公倍数,不难想到,对于M=p1x1×p2x2…M = p_1^{x_{1}} \times p_2^{x_{2}} \ldotsM=p1x1×p2x2…,每个pixjp_{i}^{x_{j}}pixj,均取自包含子数组中包含pip_{i}pi这个因子的最高次项,那么,如果想要组成数字MMM,就需要保证子数字对于pixjp_{i}^{x_{j}}pixj,均能至少取出其中一个数字包含该因子,同时该数字包含素因子pip_{i}pi的次数不能超过xjx_{j}xj。
因此,可以将输入的数字转化为仅保留该数字包含数字MMM中pixjp_{i}^{x_{j}}pixj这些因子的形式,即如果包含因子pip_ipi,但次数低于xjx_{j}xj,就将该因子去掉,因为该因子不会对最小公倍数产生贡献,然后将剩余的数字存储到数组中,即使用cnt[i]cnt[i]cnt[i]表示对答案产生iii贡献的数字个数。
如果包含了其他因子,或因子次数过高,那么必然不能属于答案的一部分,可以将该数字直接舍去。
然后考虑初始方案,不难想到,对于cnt[0]cnt[0]cnt[0],这些数字不会对答案产生贡献,但也不会影响到答案,因此,任取这些数字均能产生一种不同的答案,那么这些数字对于每组方案均能产生2cnt[0]2^{cnt[0]}2cnt[0]的贡献。
使用二进制枚举对组成数字MMM的素因子进行枚举,并使用背包算法记录方案数量(当二进制枚举枚举到的数字有numnumnum个,不难想到,只要结果中包含至少一个该数字,就能保证结果包含这些数字对应的因子,那么取这些数字的方案总数为2num−12^{num} - 12num−1,减去的1为全部数字均不取的方案数)。
如MMM的素因子有TTT种,那么最后的答案即为dp[(1<<T)−1]dp[(1 << T) - 1]dp[(1<<T)−1],其中(1<<T)−1(1 << T) - 1(1<<T)−1为长度为TTT的全111二进制串,表示包含了MMM的所有的素因子。
坑点
当M=1M = 1M=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,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

705





