AtCoder Beginner Contest 350 A~G

A.Past ABCs(判断)

题意

给出一个包含666个字符的字符串,保证前面三个字符为ABCABCABC,后面三个字符均为数字。

问,这个字符串代表的比赛是否存在?

分析

将后面三个字符转化为数字,然后判断这一场是否在范围内(即id≤349id \le 349id349 andandand id≥1id \ge 1id1 andandand id≠316id \ne 316id=316)即可。

代码

#include<bits/stdc++.h>
using namespace std;

void solve() {
    string s;
    cin >> s;
    int num = 0;
    for (int i = 3; i <= 5; i++) {
        num = num * 10 + s[i] - '0';
    }
    if (num >= 350 || num <= 1 || num == 316) {
        cout << "No" << endl;
    } else {
        cout << "Yes" << endl;
    }
}

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

B.Dentist Aoki(模拟)

题意

高桥有NNN颗牙齿,牙医将对这些牙齿进行以下操作QQQ次:

  • 对于第iii次操作,选择第TiT_iTi颗牙齿

  • 如果第TiT_iTi颗牙齿存在,那么拔掉它

  • 如果第TiT_iTi颗牙齿不存在,那么种一颗牙齿上去

问,结束操作之后,高桥还剩多少颗牙齿?

分析

使用数组记录每颗牙齿的状态,如果是偶数表示存在,将牙齿总数减少1,如果是奇数表示不存在,将牙齿的数量增加一。

代码

#include<bits/stdc++.h>
using namespace std;

int P[1005];
void solve() {
    int n, k;
    cin >> n >> k;
    for (int i = 1; i <= k; i++) {
        int a;
        cin >> a;
        P[a]++;
        if (P[a] & 1) n--;
        else n++;
    }
    cout << n << endl;
}

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

C.Sort(思维)

题意

给出一个包含NNN个数字的数组AAA,保证AAA是数字1∼N1 \sim N1N的一个排列,你可以执行若干次以下操作:

  • 选择两个不同的下标i,ji, ji,j,交换iii下标和jjj下标上的数字。

问,最少执行多少次操作,才能使得数组AAA变为升序排序,请你输出最小的操作次数以及每次交换位置的两个下标。

分析

将数组AAA中数字放置与下标1∼N1 \sim N1N上,那么只要出现A[i]≠iA[i] \ne iA[i]=i,必然就需要交换,且必然是跟数字iii所在的下标进行交换。

如果直接使用遍历的方式去后面进行查找,那么就会因为时间复杂度过高而导致超时。

那么该怎么办呢?

可以使用数组记录每个数字的位置,直接通过这个数组找到该交换的数字所在的下标,并模拟交换即可。

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5e2;
int n, a[N], pos[N];
vector<pair<int, int> > ans;
void solve() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        pos[a[i]] = i;
    }
    for (int i = 1; i <= n; i++) {
        if (a[i] != i) {
            int p = pos[i];
            pos[a[i]] = p;
            swap(a[i], a[p]);
            ans.push_back({i, p});
        }
    }
    cout << ans.size() << endl;
    for (auto i : ans) {
        cout << i.first << ' ' << i.second << endl;
    }
}

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

D.New Friends(DFS)

题意

给出NNN个人,和MMM对关系Ai,BiA_i, B_iAi,Bi,每对关系表示AiA_iAiBiB_iBi互为朋友,问:你最多可以执行多少次以下操作:

  • 选择三个人X,Y,ZX, Y, ZX,Y,Z,且XXXYYY是朋友,YYYZZZ是朋友,但XXXZZZ不是朋友,让XXXZZZ成为朋友

分析

不难发现,实际上本题就是给出了若干个无向连通图,问这些连通子图均成为完全图还需要多少条边。

使用dfs记录每个连通子图中包含的点的数量cntcntcnt,并计算该子图若为完全图应该包含的边数(cnt×(cnt−1)2\frac{cnt \times (cnt - 1)}{2}2cnt×(cnt1))。

统计完所有子图成为完全图的边数后,减去已有的边数MMM极为最后的答案。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5e2;
vector<int> G[N];

int n, m,vis[N];
ll ans, cnt;

void dfs(int x) {
    cnt++;
    for (auto i : G[x]) {
        if (vis[i]) continue;
        vis[i] = 1;
        dfs(i);
    }
}
void solve() {
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        int u, v;
        cin >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    for (int i = 1; i <= n; i++) {
        if (vis[i] == 0) {
            cnt = 0;
            vis[i] = 1;
            dfs(i);
            ans += (cnt - 1) * cnt / 2;
        }
    }
    cout << ans - m << endl;
}

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

E.Toward 0(记忆化搜索)

题意

给出一个数字NNN,你可以进行若干次以下操作:

  • 支付XXX元,将NNN变为⌊NA⌋\lfloor \frac{N}{A} \rfloorAN

  • 支付YYY元,等概率的从1∼61 \sim 616选择一个整数bbb,并将NNN变为⌊Nb⌋\lfloor \frac{N}{b} \rfloorbN

问:最优策略下,所需花费的期望是多少?

分析

由于每次操作均有两种选择, 那么对于两种选择必然会选期望花费较小的一个。

可以使用dfs(n)dfs(n)dfs(n)搜索数字nnn的最低期望花费,那么每轮有以下两种选择:

  • 选择操作1,期望花费为dfs(n/a)+Xdfs(n / a) + Xdfs(n/a)+X

  • 选择操作2,期望花费为∑i=26dfs(n/i)+Y5+Y\frac{\sum\limits_{i = 2}^{6}dfs(n / i) + Y}{5} + Y5i=26dfs(n/i)+Y+Y(主要不要选择b为1进入搜索,会导致进入死循环)

Tips: 操作2相当于取5次有效操作的期望(∑i=26dfs(n/i)+Y5\frac{\sum\limits_{i = 2}^{6}dfs(n / i) + Y}{5}5i=26dfs(n/i)+Y),加上一次无效操作的额外花费(Y)(Y)(Y)

每轮选择期望花费较小的操作,并进行记忆化即可。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;

map<ll, db> dp;

ll n, a, x, y;

db dfs(ll num) {
    if (num <= 0) return 0;
    if (dp.find(num) != dp.end()) return dp[num];
    db cost_x = dfs(num / a) + x;
    db cost_y = 0;
    for (int i = 2; i <= 6; i++) {//注意:1操作相当于不操作
        cost_y += dfs(num / i);
    }
    cost_y = (cost_y + y) / 5 +  y;
    return dp[num] = min(cost_x, cost_y);
}

void solve() {
    cin >> n >> a >> x >> y;
    cout << fixed << setprecision(15) << dfs(n) << endl;
}

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

F.Transpose(模拟)

题意

给出一个字符SSS,你需要将所有匹配的括号中的字符串进行翻转,并将字母大小写互换,然后去掉括号,问操作结束后的字符串是什么。

分析

预处理匹配的两个括号对应的另一个括号所在的下标,然后使用搜索进行模拟。

如果遇到括号,根据记录的下标信息将括号中的内容递归处理,并跳过这段区间。

如果遇到字母,根据当前翻转次数决定当前字母是否需要进行大小写转换。

递归时还需要根据翻转次数判断当前应该从后往前遍历还是从前往后遍历。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e5 + 5e2;

int n, pos[N];
string s;
stack<int> st;

char get(char c, int change) {
    if (change) {
        if (c >= 'a' && c <= 'z') return c - 32;
        return c + 32;
    }
    return c;
}

void dfs(int left, int right, int dir, int change) {
    if (left > right) return;
    if (dir) {
        for (int i = left; i <= right; i++) {
            if (s[i] == '(') {
                dfs(i + 1, pos[i] - 1, dir ^ 1, change ^ 1);
                i = pos[i];
            } else {
                cout << get(s[i], change);
            }
        }
    } else {
        for (int i = right; i >= left; i--) {
            if (s[i] == ')') {
                dfs(pos[i] + 1, i - 1, dir ^ 1, change ^ 1);
                i = pos[i];
            } else {
                cout << get(s[i], change);
            }
        }
    }
}

void solve() {
    cin >> s;
    n = s.size();
    s = "$" + s;
    for (int i = 1; i <= n; i++) {
        if (s[i] == '(') {
            st.push(i);
        }
        if (s[i] == ')') {
            pos[i] = st.top();
            pos[st.top()] = i;
            st.pop();
        }
    }
    dfs(1, n, 1, 0);
}

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

G.Mediator(bitset,分治)

题意

Tips: 本题的输入方式特殊,且内存限制较小

有一张包含NNN个点的图,将对这个图进行QQQ次操作,每次操作为以下两种操作之一:

  • 1 u v:在点uuu和点vvv之间建一条边

  • 2 u v:检查是否存在某个点aaa,同时满足aaauuu直接连接,且aaavvv直接连接,如果有,返回这个点的编号,没有,返回0

开始时,有X=0X = 0X=0

每行将输入三个正整数a,b,ca, b, ca,b,c,你需要通过以下计算得到正确的操作A B C:

  • A=1+(((a×(1+X))mod 998244353)mod 2)A = 1 + (((a \times (1 + X)) mod \text{ } 998244353) mod \text{ } 2)A=1+(((a×(1+X))mod 998244353)mod 2)

  • B=1+(((b×(1+X))mod 998244353)mod 2)B = 1 + (((b \times (1 + X)) mod \text{ } 998244353) mod \text{ } 2)B=1+(((b×(1+X))mod 998244353)mod 2)

  • C=1+(((c×(1+X))mod 998244353)mod 2)C = 1 + (((c \times (1 + X)) mod \text{ } 998244353) mod \text{ } 2)C=1+(((c×(1+X))mod 998244353)mod 2)

每次操作2结束后,XXX会变为操作2查询的结果。

分析

考虑使用bitset记录每个点连接的点的信息,即使用bt[i][j]=0bt[i][j] = 0bt[i][j]=0表示点iii有一条连接点jjj的边,bt[i][j]=0bt[i][j] = 0bt[i][j]=0表示没有。

那么每次想知道同时连接点uuu和点vvv的点时,可以直接计算bt[u]bt[u]bt[u] ^ bt[v]bt[v]bt[v],如果结果不为全000的二进制串,那么找到那个111所在的位置即为答案,否则,就是不存在这个点。

而由于题目内存限制较小,如果直接使用bitset会因为内存超限而无法通过,因此,需要进行优化。

将问题分成两部分,将编号较小的点使用bitset进行维护,将编号较大的点使用vector进行维护。

这样,就能保证时间和空间均能在题目规定范围内完成。

Tips

  1. 通过计算可以发现,只要bitset的长度小于等于2×1042 \times 10^{4}2×104,那么就不会出现内存超限

  2. 本题卡了bitset的内存,但没考虑卡掉vector模拟的时间,因此直接使用vector进行模拟可以通过

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5e2, M = 1e4;
const int mod = 998244353;
int n, q, vis[N];
ll x;
bitset<M> bt[N];
vector<int> G[N];

void solve() {
    ll a, b, c;
    cin >> a >> b >> c;
    a = 1 + ((a * (1 + x)) % mod) % 2;
    b = 1 + ((b * (1 + x)) % mod) % n;
    c = 1 + ((c * (1 + x)) % mod) % n;
    if (a == 1) {
        if (c >= M) G[b].push_back(c);
        else bt[b].set(c);
        if (b >= M) G[c].push_back(b);
        else bt[c].set(b);
    } else {
        auto num = bt[b] & bt[c];
        if (num.count())x = num._Find_first();
        else {
            x = 0;
            memset(vis, 0, sizeof(vis));
            for (auto i : G[b]) vis[i] = 1;
            for (auto j : G[c]) if (vis[j]) {
                x = j;
                break;
            }
        }
        cout << x << endl;
    }
}

int main() {
    cin >> n >> q;
    while (q--) {
        solve();
    }
    return 0;
}

赛后交流

在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。

群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值