Educational Codeforces Round 161 (Div.2) A~F

A.Tricky Template (模拟)

题意:

询问是否存在一个字符串模板ttt使得字符串aaabbb与之匹配,ccc不匹配,匹配条件如下:

  • 如果模板中第iii个字母是小写字母,那么sis_isi必须与tit_iti相同。
  • 如果模板中的第iii个字母是大写字母,那么sis_isi必须与tit_iti的小写字母不同。例如,如果模板中有字母AAA,则在字符串的相应位置不能使用字母aaa

分析:

如果字符串ccc中有一个字符和字符串aaa,字符串bbb都不同就存在符合条件的字符串模板ttt

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;

int main() {
    int t;
    cin >> t;
    while (t--) {
        int n;
        string a, b, c;
        cin >> n >> a >> b >> c;
        bool flag = 0;
        for (int i = 0; i < n; i++)
            if (c[i] != a[i] && c[i] != b[i]) {
                flag = 1;
                break;
            }

        if (flag)
            cout << "YES" << endl;
        else
            cout << "NO" << endl;
    }
    return 0;
}

B.Forming Triangles (模拟)

题意:

nnn根木棍,第iii根木棍长度为2ai2^{a_i}2ai。询问从nnn根木棍中选出333根木棍组成一个三角形的方案数。

分析:

先使用计数排序(桶的思想)统计各个数字的出现次数,然后考虑合法的情况。

合法的情况有两种:

  • 同种长度木棍选择两根,再选一根短的木棍。
  • 选择长度相等的三根木棍。

简单组合数公式计算即可。

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;

int main() {
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        vector<LL> a(n + 1);
        for (int i = 0; i < n; i++) {
            int x;
            cin >> x;
            a[x]++;
        }
        LL ans = 0;
        LL lst = 0;
        for (int i = 0; i <= n; i++) {
            LL x = a[i];
            if (x >= 2) {
                ans += x * (x - 1) / 2 * lst;
            }
            if (x >= 3) {
                ans += x * (x - 1) * (x - 2) / 6;
            }
            lst += x;
        }
        cout << ans << endl;
    }
    return 0;
}

C. Closest Cities (前缀和)

题意:

nnn个城市分布在一个数轴上,第iii个城市的坐标为aia_iai,保证aia_iai升序,并保证对于每一个iii,距离他最近的城市只有一个。有两种城市间移动的方式:

  • 花费111元转移到当前所在城市最近的城市。

  • 花费∣ax−ay∣\vert a_x-a_y \vertaxay元,从xxx移动到yyy

给出mmm次询问,每次给出两个城市xxx,yyy,询问从xxxyyy最少需要花费多少元。

分析:

可以发现,最优的移动方式一定是尽量选择转移到离当前城市最近的城市。可以分别计算从前到后和从后到前两个方向的花费,分别维护从前到后和从后到前的前缀和即可。

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const LL inf = 1e18;

int main() {
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        vector<LL> a(n + 2);
        for (int i = 1; i <= n; i++)
            cin >> a[i];
        a[0] = -inf;
        a[n + 1] = inf;
        vector<LL> sum1(n + 2), sum2(n + 2);
        for (int i = 1; i <= n; i++) {
            if (a[i + 1] - a[i] < a[i] - a[i - 1])
                sum1[i + 1] = sum1[i] + 1;
            else
                sum1[i + 1] = sum1[i] + a[i + 1] - a[i];
        }
        for (int i = n; i >= 1; i--) {
            if (a[i] - a[i - 1] < a[i + 1] - a[i])
                sum2[i - 1] = sum2[i] + 1;
            else
                sum2[i - 1] = sum2[i] + a[i] - a[i - 1];
        }
        int m;
        cin >> m;
        for (int i = 1; i <= m; i++) {
            int x, y;
            cin >> x >> y;
            LL ans = 0;
            if (y > x)
                ans = sum1[y] - sum1[x];
            else
                ans = sum2[y] - sum2[x];
            cout << ans << endl;
        }
    }
    return 0;
}

D.Berserk Monsters(模拟)

题意:

Monocarp在打怪。

一排有nnn个怪物,编号从111nnn。其中第iii个怪物有两个参数:攻击值等于aia_iai,防御值等于did_idi。为了杀死这些怪物,Monocarp给它们施放了狂暴咒语,因此它们会互相攻击,而不是攻击Monocarp。

战斗由nnn个回合组成。每回合都会发生以下情况:

  • 首先,每个活着的怪物iii都会对左边最近的活着的怪物(如果存在)和右边最近的活着的怪物(如果存在)造成aia_iai伤害;
  • 然后,每只在本回合中受到超过djd_jdj伤害的活着的怪物jjj都会死亡。也就是说,当且仅当第jjj个怪物的防御值djd_jdj小于它在本轮受到的总伤害时,它才会死亡。

计算每一轮中死亡的怪物数量。

分析:

这道题的本质是模拟,我们可以想到对于每个结点,我们都需要找他的左右两个相邻的结点,因此我们采取链表的写法,用链表维护相邻的怪物,随后在每一次遍历的过程中找到死亡的结点,随后对左右节点的前驱和后继进行改变。每一轮遍历都会将死亡的结点进行消除,即改变了死亡结点的前驱和后继,因此我们每次对上一轮被消灭的怪物相邻的怪物进行处理即可。

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const LL mod = 998244353;
const LL N = 5e5;
int n, a[N], d[N], killed[N];
int l[N], r[N];

void Solve() {
    queue<int> q1, q2, q3;
    int len = 0;
    for (int i = 1; i <= n; i++)
        q1.push(i);
    for (int i = 1; i <= n; i++) {
        int ans = 0;
        while (!q1.empty()) {
            int x = q1.front();
            q1.pop();
            if (killed[x] || x <= 0 || x > n)
                continue;
            if (d[x] < a[l[x]] + a[r[x]]) {
                if (!killed[x]) ++ans, killed[x] = 1;
                q2.push(x);
            }
        }
        cout << ans << " ";
        len++;
        if (ans == 0)
            break;

        while (!q2.empty()) {
            int x = q2.front();
            q2.pop();
            q3.push(x);
            r[l[x]] = r[x];
            l[r[x]] = l[x];
        }
        while (!q3.empty()) {
            int x = q3.front();
            q3.pop();
            q1.push(l[x]), q1.push(r[x]);
        }
    }
    for (int i = len + 1; i <= n; i++)
        cout << "0 ";
    cout << endl;
}

int main() {
    int T;
    cin >> T;
    while (T--) {
        cin >> n;
        a[0] = a[n + 1] = 0;
        d[0] = d[n + 1] = 0;
        for (int i = 1; i <= n; i++)
            cin >> a[i];
        for (int i = 1; i <= n; i++) {
            killed[i] = 0;
            l[i] = i - 1, r[i] = i + 1;
        }
        for (int i = 1; i <= n; i++)
            cin >> d[i];
        Solve();
    }
    return 0;
}

E.Increasing Subsequences(构造)

题意:

数组aaa的递增子序列是指在不改变其余元素顺序的情况下,通过移除某些元素而得到的序列,并且其余元素是严格递增的(即ab1<ab2<⋯<abka_{b_1} \lt a_{b_2} \lt \dots \lt a_{b_k}ab1<ab2<<abkb1<b2<⋯<bkb_1 \lt b_2 \lt \dots \lt b_kb1<b2<<bk)。注意,空子序列也是递增的。

给你一个正整数XXX。找出一个长度最多200200200的整数数组,使得它恰好有XXX个递增的子序列,若没有这样的数组,输出−1-11。如果有多个答案,输出其中任何一个。

如果两个子序列由相同的元素组成,但对应数组中的不同位置,则认为它们是不同的(例如,数组[2,2][2,2][2,2]有两个不同的子序列等于[2][2][2])。

分析:

考虑从大往小地向序列中加数。为了最小化所需数量至少要有一个长度为⌊log⁡2X⌋⌊\log2X⌋log2X的递增数列,考虑能否复用这个递增数列中的某些元素来凑出其他222的幂出来。

假设当前序列的答案为kkk,若在当前已有数组的右边加一个最大值,或者加一个比之前所有数都小的数在最左边,那么新生成的序列的答案就为2k2k2k,加一个比之前所有数都小的数在右边的话新生成的序列的答案就为k+1k+1k+1

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const LL mod = 998244353;
const LL N = 5e5;
LL t, X;

int main() {
    cin >> t;
    while (t--) {
        cin >> X;
        LL high;
        for (LL i = 61; i >= 0; --i) {
            if ((X >> i) & 1LL) {
                high = i;
                break;
            }
        }
        deque<int> ans;
        ans.push_back(0);
        LL lst = 0;
        for (LL i = high - 1; i >= 1; --i) {
            if ((X >> i) & 1LL) {
                ans.push_back(--lst);
                ans.push_front(--lst);
            } else
                ans.push_front(--lst);
        }
        if (X & 1LL)
            ans.push_back(--lst);;
        cout << ans.size() << endl;
        for (auto x: ans) {
            cout << x << " ";
        }
        cout << endl;
    }
    return 0;
}

F.Replace on Segment(区间DP)

题意:

给你一个数组a1,a2,…,ana_1,a_2,\dots,a_na1,a2,,an,其中每个元素都是从111xxx的整数。

你可以多次对它进行下面的操作:

  • 选择三个整数lllrrrkkk,使得1≤l≤r≤n1 \le l \le r \le n1lrn1≤k≤x1 \le k \le x1kx和一个元素aia_iai,使得l≤i≤rl \le i \le rlirkkk不同。然后,对于每个i∈[l,r]i \in [l, r]i[l,r],用kkk替换aia_iai

换句话说,在数组中选择一个子段,并从111xxx中选择一个未在该子段中出现的整数,然后用该整数替换子段中的每个元素。

为使数组中的所有元素相等。最少需要进行多少次操作?

分析:

fl,r,jf_{l,r,j}fl,r,j为使区间[l,r][l,r][l,r]元素都为jjj的最小操作数,设数组gl,r,jg_{l,r,j}gl,r,j表示在区间[l,r][l,r][l,r]中没出现过元素jjj的方案数。

考虑fff数组转移。第一种是直接从ggg转移到fff,即对没出现过jjj的区间[l,r][l,r][l,r]直接耗费111全部变成jjj。即:fl,r,j=gl,r,j+1f_{l,r,j}=g_{l,r,j}+1fl,r,j=gl,r,j+1。第二种是区间合并,需要枚举中转点midmidmid,此时fl,r,j=fl,mid,j+fmid+1,r,jf_{l,r,j}=f_{l,mid,j}+f_{mid+1,r,j}fl,r,j=fl,mid,j+fmid+1,r,j

考虑ggg数组转移。第一种是从ggg转移,ggg数组需要变成没有出现过数zzz的最小方案数,即gl,r,k=gl,r,j+1g_{l,r,k}=g_{l,r,j}+1gl,r,k=gl,r,j+1
第二种仍为区间合并,枚举中转点即可,即gl,r,j=gl,mid,j+gmid+1,r,jg_{l,r,j}=g_{l,mid,j}+g_{mid+1,r,j}gl,r,j=gl,mid,j+gmid+1,r,j

那么,最后的答案即为min(f1,n,1,f1,n,2,...,f1,n,x)min(f_{1, n, 1}, f_{1, n, 2}, ..., f_{1, n, x})min(f1,n,1,f1,n,2,...,f1,n,x)

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const LL mod = 998244353;
const LL N = 155;
int n, x, a[N], f[N][N][N], g[N][N][N], tmp[N][N][N];

void solve() {
    cin >> n >> x;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        for (int j = 1; j <= x; j++) {
            if (j != a[i]) {
                g[i][i][j] = 0;
                f[i][i][j] = 1;
            }
        }
        g[i][i][a[i]] = 1;
        f[i][i][a[i]] = 0;
    }
    for (int len = 2; len <= n; len++) {
        for (int l = 1; l + len - 1 <= n; l++) {
            int r = l + len - 1;
            for (int j = 1; j <= x; j++) {
                for (int mid = l; mid < r; mid++) {
                    f[l][r][j] = min(f[l][r][j], f[l][mid][j] + f[mid + 1][r][j]);
                    tmp[l][r][j] = min(tmp[l][r][j], g[l][mid][j] + g[mid + 1][r][j]);
                }
                f[l][r][j] = min(f[l][r][j], tmp[l][r][j] + 1);
                for (int k = 1; k <= x; k++) {
                    if (k == j) {
                        continue;
                    }
                    g[l][r][k] = min(g[l][r][k], tmp[l][r][j] + 1);
                }
            }
            for (int j = 1; j <= x; j++) {
                g[l][r][j] = min(g[l][r][j], tmp[l][r][j]);
            }
        }
    }
    int ans = n;
    for (int i = 1; i <= x; i++) {
        ans = min(ans, f[1][n][i]);
    }
    cout << ans << endl;
}

int main() {
    int T;
    cin >> T;
    while (T--) {
        memset(f, 0x3f, sizeof(f));
        memset(g, 0x3f, sizeof(g));
        memset(tmp, 0x3f, sizeof(tmp));
        solve();
    }
    return 0;
}

学习交流

以下为学习交流QQ群,群号: 546235402,每周题解完成后都会转发到群中,大家可以加群一起交流做题思路,分享做题技巧,欢迎大家的加入。

### 关于Educational Codeforces Round 175 对于Div. 2参赛者的信息 针对编号为175的教育轮次比赛,在Codeforces平台上的此类赛事通常面向不同级别的编程爱好者开放,但有着特定的规定来区分参与者的分组。对于Div. 2的参与者而言,此级别通常是为那些评级低于2100的程序员准备的比赛环境[^1]。 值得注意的是,虽然提及到trusted participants的概念主要适用于第三级别的正式排名表单中的成员资格定义,即仅限参加了至少两个评分赛(每次比赛中解决了一个以上的问题),并且未曾达到过1900或更高的分数的选手才能成为受信任的第三级别成员;然而这一规定并不直接影响Div. 2参赛者的分类标准。 为了获取关于Educational Codeforces Round 175更具体的数据,比如确切的时间安排、题目列表以及特殊规则等细节,建议访问官方公告页面查看由主办方发布的最新消息和指南。这些资源能够提供最权威的第一手资料给有兴趣参加该活动的人士。 ```python # Python代码示例用于展示如何通过API查询比赛信息(假设存在这样的功能) import requests def get_contest_info(contest_id): url = f"https://codeforces.com/api/contest.standings?contestId={contest_id}" response = requests.get(url).json() if 'result' in response: contest_data = response['result']['contests'][0] return { "name": contest_data["name"], "startTimeSeconds": contest_data["startTimeSeconds"], "durationSeconds": contest_data["durationSeconds"] } else: return None print(get_contest_info(175)) ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值