AtCoder Beginner Contest 337 A~G

本文对AtCoder的多道算法题进行了解析,涵盖循环、模拟、前缀和、交互、二进制枚举、双指针等多种算法类型。详细阐述了各题的题意、分析思路及对应代码,还给出学习交流QQ群,方便大家交流做题思路与技巧。

A.Scoreboard(循环)

题意:

两个队伍进行NNN场比赛。在第iii场比赛中(1≤i≤N)(1≤i≤N)1iN,两队各得到XiX_iXiYiY_iYi分。比赛结束后总分更高的一方获胜。输出胜利的队伍。如果两支队伍总分相同,则为平局。

分析:

按题意模拟,进行累加然后比较总分大小即可。

代码:

#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(模拟)

题意:

定义扩展AAA字符串、扩展BBB字符串、扩展CCC字符串和扩展ABCABCABC字符串如下:

  • 如果SSS中的所有字符都是AAA,则SSS是一个扩展AAA字符串。
  • 如果SSS中的所有字符都是BBB,则SSS是一个扩展BBB字符串。
  • 如果SSS中的所有字符都是CCC,则SSS是一个扩展CCC字符串。
  • 如果存在一个扩展AAA字符串SAS_ASA,一个扩展BBB字符串SBS_BSB和一个扩展CCC字符串SCS_CSC,使得按照顺序连接SAS_ASASBS_BSBSCS_CSC所得到的串等于SSS,则称SSS为一个扩展ABCABCABC字符串。

例如,ABCABCABCAAAAAABBBCCCCCCCAAABBBCCCCCCCAAABBBCCCCCCC都是扩展ABCABCABC字符串,但ABBAAACABBAAACABBAAACBBBCCCCCCCAAABBBCCCCCCCAAABBBCCCCCCCAAA不是。

注意,空串既可以看作是一个扩展AAA串,也可以看作是一个扩展BBB串或者扩展CCC串。给定由字母AAABBBCCC组成的一个字符串SSS 。若SSS是一段扩展ABCABCABC字符串,输出YesYesYes;否则,输出NoNoNo

分析:

题意为给定一个字符串,判断是否形为按顺序出现数个AAA,数个BBB,数个CCC(个数可以为000)。可以使用uniqueuniqueunique函数去重后判断是否符合要求的顺序,也可以循环遍历字符串判断。

代码:

#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(模拟)

题意:

NNN个人站在一条线上:编号为1−N1-N1N。人们的排列方式是一个长度为NNN的序列A=(A1,A2,...,AN)A=(A_1,A_2,...,A_N)A=(A1,A2,...,AN)AiA_iAi(1≤i≤N)(1≤i≤N)1iN,表示以下信息:如果Ai=−1A_i=-1Ai=1,则第iii个人在队伍最前面;如果Ai≠−1A_i≠-1Ai=1,则第iii个人紧随着第AiA_iAi个人后面。按照从前到后的顺序输出队伍中每个人的编号。

分析:

nnn个人站成一排,给定每个人位于某人的右边,求这个排列。

对序列AAA进行映射,令r[i]=jr[i]=jr[i]=j,表示第jjj个人在第iii个人的右边。找到最左边的人lll,即值为−1-11的人,不断更新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(前缀和)

题意:

有一个HHHWWW列的网格。用(i,j)(i,j)(i,j)表示从上到下第iii行、从左到右第jjj列的单元格。每个单元格包含字符′o′'o'o′x′'x'x′.′'.'.。单元格中的字符由长度为WWWHHH个字符串S1,S2,...,SHS_1,S_2,...,S_HS1,S2,...,SH来表示;单元格(i,j)(i,j)(i,j)中的字符是字符串SiS_iSi的第jjj个字符。

对于这个网格,可以重复以下操作任意次数(可能为零):

  • 选择一个带有′.′'.'.字符的单元格,并将该单元格中的字符更改为′o′'o'o

判断是否可能通过操作使得网格中存在KKK个水平或垂直的连续为′o′'o'o的单元格(即满足以下两种条件之一)。如果可能,输出实现此目标所需的最小操作数。

  • 存在整数对(i,j)(i,j)(i,j),满足1≤i≤H1≤i≤H1iH1≤j≤W−K+11≤j≤W−K+11jWK+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+K1)中的字符都是′o′'o'o
  • 存在整数对(i,j)(i,j)(i,j),满足1≤i≤H−K+11≤i≤H−K+11iHK+11≤j≤W1≤j≤W1jW,使得在单元格(i,j),(i+1,j),…,(i+K−1,j)(i,j),(i+1,j),…,(i+K−1,j)(i,j),(i+1,j),,(i+K1,j)中的字符都是′o′'o'o

分析:

本题可以暴力枚举,对每一行,每一列都判断一次,取最小代价输出。枚举这连续kkk个格子的左端点 ,然后往右的kkk个格子里,不能有′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;
    else
        cout << ans << endl;
}

int main() {
    solve();
    return 0;
}

E.Bad Juice(交互、二进制枚举)

题意:

NNN瓶果汁,编号从111NNN。其中恰好有一瓶变质了。即使喝一小口变质的果汁,第二天也会拉肚子。高桥必须在第二天之前确定出哪瓶果汁变质了。为此,他决定打电话给尽可能少的朋友,并将其中的NNN瓶果汁分给他们。他可以给每个朋友任意数量的瓶子,并且每瓶果汁都可以分给任意个朋友。输出要打电话的朋友数以及如何分配果汁,然后接收关于每个朋友是否在第二天拉肚子的信息,并输出变质果汁瓶子的编号。

分析:

因为每位朋友是否拉肚子这个信息只有111000两种情况,可以先将饮料的编号转化成二进制,需要朋友的数量就是NNN在二进制中的位数。

对于朋友iii,让他喝在二进制中,编号第iii位为111的饮料。如果第iii位朋友拉了肚子,就可以知道变质的饮料第iii位为111

考虑如果NNN是二的整数次幂,那么饮料NNN会且仅会被第MMM位朋友喝,第MMM位朋友喝且仅喝饮料NNN。在这种情况下,我们就可以少叫一位朋友,并且让饮料NNN不被任何人喝。

如果所有朋友都不拉肚子,就可以判断饮料NNN变质了。

代码:

#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(双指针、思维)

题意:

给定正整数NNNMMMKKK和长度为NNN的正整数序列(C1,C2,…,CN)(C_1,C_2,…,C_N)(C1,C2,,CN)。对于每个r=0,1,2,…,N−1r=0,1,2,…,N−1r=0,1,2,,N1,输出以下问题的答案:

有一个由NNN个彩色球组成的序列。编号为i=1,2,…,Ni=1,2,…,Ni=1,2,,N,从序列开头开始第iii个球的颜色是CiC_iCi。此外,还有MMM个编号为111MMM的空盒子。在执行以下操作后确定盒子中球的总数目。

  • 首先,进行以下操作rrr次:将序列中最前面的球移到序列末尾。
  • 然后,重复以下操作直至序列中至少还剩一个球:如果存在一个盒子,其中装有至少一个但不多于KKK个与序列最前面的球相同颜色的球,则将最前面的球放入该盒子。如果没有这样的盒子:则若存在一个空箱子,将最前面的球放入编号最小的那个空箱子。如果没有空箱子,则拿走最前面的球而不把它放进任何箱子。

分析:

首先执行rrr次,循环询问,对于每个开头的长为nnn的序列都询问一次,将序列扩成二倍。

考虑双指针,使用双指针维护第一个不在盒子内的球的位置,手动模拟一下可以发现它是单调递增。并且,一旦确定了不在盒子内的球的位置,设为xxx,那么前面的盒子内的每种颜色球的个数可以被确定,同时这些球占的盒子的个数也可被确定。位置在xxx位置之后,但因为和xxx位置之前放入盒子里的球同色,而能被继续放入盒子的球,也可以被确定。

假设某种颜色总共有aaa个球,当前占了bbb个盒子,则这种颜色的球总共放了min(b∗k,a)min(b*k,a)min(bk,a)个。一旦确定了不在盒子内的球的位置xxx,前面在盒子里的同色的球的个数也能确定。记颜色ccc的球在盒子里的球为num[c]num[c]num[c]个,则占了⌈num[c]k⌉\lceil \frac{num[c]}{k} \rceilknum[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

题意:

给定一棵具有NNN个顶点的树TTT,顶点编号为顶点111,顶点222,…,顶点NNN。第iii条边(1≤i<N)(1 \le i < N)(1i<N)连接顶点uiu_iuiviv_ivi

对于树TTT中的一个顶点uuu,定义f(u)f(u)f(u)如下:f(u)f(u)f(u)表示满足以下两个条件的包含顶点vvvwww的顶点对的对数:

  1. 顶点www在连接uuuvvv的路径中;
  2. v<wv < wv<w

u=wu=wu=wv=wv=wv=w时,顶点www也包含在连接顶点uuuvvv的路径中。

计算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) 对于哪个uuu做出贡献。由于v<wv < wv<w,满足的条件是在u,vu,vu,v路径上存在www。这可以重新表述为“在去除了www的森林中,vvv属于的连通分量不包含uuu”。考虑以任意顶点为根节点的有根树,则满足该条件的uuu集合可以用部分子树或者除了某个特定部分子树之外全部的形式表示。固定部分子树,并考虑每种形式对应的组(v,w)(v,w)(v,w)所做出的贡献。

  1. 考虑以部分子树顶点ppp及其后代构成的部分子树。满足(v,w)(v,w)(v,w)对应贡献给顶点uuu与该部分子树相同的(v,w)(v<w)(v,w)(v < w)(v,w)(v<w),需要满足以下条件:w=pw=pw=pvvv不包含在该部分子树中。这样的组(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)得到。

  2. 考虑除了特定某个部分子树之外全部顶点ppp及其后代构成的部分子树。满足(v,w)(v,w)(v,w)对应贡献给顶点uuu与该部分子树以外全部相同的(v,w)(v<w)(v,w)(v < w)(v,w)(v<w),需要满足以下条件:wwwppp的父节点且vvv包含在该部分子树中。这样的组(v,w)(v,w)(v,w)数量可通过计算(该部分子树中小于www的顶点数xxx)得到。

综上所述,如果能对于每个部分子树和整数qqq进行快速求解,(该部分子树中小于qqq的顶点数xxx),就可以计算出各自的贡献。相当于DFSDFSDFS遍历顺序并在平面上进行查询。按DFSDFSDFS遍历顺序进行扫描,可以归结为列的前缀和。可以使用二进制索引或线段树进行高效查询。

代码:

#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);
    else
        update(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,每周题解完成后都会转发到群中,大家可以加群一起交流做题思路,分享做题技巧,欢迎大家的加入。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值