AtCoder Beginner Contest 349 A~F

A. Zero Sum Game(思维)

题意

NNN个人一起参加游戏,开始时每个人均拥有000点分数。每轮游戏只会在两个人之间进行,胜者将得到111分,败者将失去111分。已知结束游戏后前N−1N - 1N1个人的分数A1,A2,…,AN−1A_1, A_2, \ldots, A_{N - 1}A1,A2,,AN1,问,最后一个人的分数是多少?

分析

既然每轮游戏将会产生两个分数111−1-11,那么对于NNN个人来说,总分总是固定为000的,因此想要知道ANA_NAN,可以使用总分减去每个人的分数,即AN=0−∑i=1N−1AiA_N = 0 - \sum\limits_{i = 1}^{N - 1}A_iAN=0i=1N1Ai

代码

#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,定义一个字符串是一个好字符串需要对于每一个大于等于111iii均满足以下要求:

  • 出现次数为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

    1. 从字符串SSS中取出一个长为333的子序列,并把这个子序列中所有字母变成大写字母。
    1. 从字符串SSS中取出一个长为222的子序列,并把这个子序列中所有字母变成大写字母,然后在这个子序列后加上一个大写字母XXX

给出两个字符串SSSTTT,问字符串TTT是不是字符串SSSAirport Code

分析

对两个字符串进行字符串匹配(注意大小写),然后根据匹配上的数量进行判断:

    1. 三个字母都匹配上了,必然成立
    1. 匹配上前两个字母,且最后一个未被匹配的字母为XXX,也成立
    1. 其他情况均不成立

代码

#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,,r1。而一个序列为好的序列需要满足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 - 1LR1之间的所有数字。

分析

由于好的序列的l=2ij,r−l=2il = 2^{i}j, r - l = 2^{i}l=2ij,rl=2i,那么为了序列数量尽可能少,取的2i2^{i}2i就要尽可能大,而l=2ijl = 2^{i}jl=2ij,则lll必须为2i2^{i}2i的倍数,因此,可以对iii从大到小进行枚举,当lll2i2^{i}2i倍数时,就可以继续检查l+2il + 2^{i}l+2i后是否会超过右边界RRR,如果也没有超过右边界,那么就表示当前序列能当前能选的所有序列中最长的一个,将当前序列的起点lll和终点l+2il + 2^{i}l+2i记录到vector中,并将起点修改为l+2il + 2^{i}l+2i继续寻找下一个序列。

Tips

  1. 2i2^{i}2i可以通过位运算1<<i1 << i1<<i快速得到
  2. 由于本题数据范围较大R≤260R \le 2^{60}R260,而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=13j=13Ai,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. 某一行某一列某一对角线已经出现放置了连续的三个同色棋子
    1. 网格中已经被棋子放满了

对于情况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}M1016,而前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

因此,可以将输入的数字转化为仅保留该数字包含数字MMMpixjp_{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} - 12num1,减去的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,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值