Educational Codeforces Round 102 (Rated for Div. 2) A~D题

写在前边

链接:Educational Codeforces Round 102 (Rated for Div. 2)

第一场打CF,过了AB两题,不过速度挺慢,C题属实没看懂,D题写了个常规做法之后TLE了还剩下几分钟就睡觉了,想到了用前缀和但是确实不知道该怎么写,最近的目标就先上1400吧。

A. Replacing Elements

链接:A题链接

题目大意:

给定一个数组a, 你可以选任意不同的三个下标i, j, k, 进行操作ai=aj+ak,问经过若干次操作之后是否能使得数组中的数ai都小于等于d

思路

一开始在场上想的是模拟一遍整个过程,最后再检查一下是否合适,看了题解之后根本不用那么麻烦,只需要检查一下数组元素的最大值是否小于等于d,或者最小的两个数相加是否小于等于d即可,若最大元素小于d那么整个数组就符合条件,若最小的两个数相加小于等于d,那么他们的和一定可以覆盖所有比d大的数,解决。

代码:

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 110;

int t, n, d, a[N];

int main() {
    cin >> t;
    while (t--) {
        cin >> n >> d;
        for (int i = 0; i < n; i++) {
            cin >> a[i];
        }
        sort(a, a + n);
        cout << ((a[0] + a[1] <= d || a[n - 1] <= d) ? "YES" : "NO") << endl;
    }

    return 0;
}

B. String LCM

链接:B题链接

题目大意:

找两个字符串的"最小公倍数"即LCM(s,t), 例如s="baba", t="ba", 那么LCM(s,t)="baba",因为"baba"1="ba"2s="aaa", t="aa", 那么LCM(s,t)="aaaaaa",因为"aaa"2="aa"3

思路

先求GCD(s,t),再求LCM(s,t)

  1. GCD(s,t),它一定是字符串的一个前缀,因此可以枚举所有的前缀,再判断某个前缀变成原来的几倍是否能变成s,t,若可以那么就是GCD(s,t)
  2. 有了GCD再求LCM那就是很简单的事了,看代码即可。

代码:

#include <iostream>
#include <string>
#include <algorithm>

using namespace std;

typedef long long ll;

int gcd(int a, int b) {
    while (b != 0) {
        a %= b;
        swap(a, b);
    }
    return a;
}

bool check(string s, string x) {
    string temp = "";
    for (int i = 0; i < s.size(); i++) {
        temp += x;
        if (temp == s) {
            return true;
        }
    }
    return false;
}

string gcd_str(string s, string t) {
    string res = "";
    int lens = s.size(), lent = t.size();
    for (int i = min(lens, lent); i >= 1; i--) {
        if (lens % i == 0 && lent % i == 0) {
            string x = s.substr(0, i);
            if (check(s, x) && check(t, x)) {
                return x;
            }
        }
    }
    return "";
}

int main() {
    int n;
    cin >> n;
    while (n--) {
        string s, t, gcd_st, res = "";
        cin >> s >> t;
        gcd_st = gcd_str(s, t);
        if (gcd_st == "") {
            puts("-1");
            continue;
        }
        int len_gcdst = gcd_st.size();
        int len_lcmst = (s.size() / gcd_st.size()) * (t.size() / gcd_st.size());

        for (int i = 0; i < len_lcmst; i++) {
            res += gcd_st;
        }
        cout << res << endl;
    }

    system("pause");
    return 0;
}
优化:可以用数学的方法求GCD(s,t)进行优化,使代码更短,效率更高。

首先判断两个字符串是否存在GCD(s,t)可以利用更简单的方法,即 s+t 是否等于 t+s,若存在GCD,那么s+t==t+s,否则就不存在。那证明一下:
如果他们有公因子"abc",那么s就会有m"abc"重复,t就会有n"abc"重复,因此:

s+t=m"abc"+n"abc"=(m+n)"abc"

而:

t+s=n"abc"+m"abc"=(n+m)"abc"

得:(m+n)"abc"=(n+m)"abc",因此s+t=t+s
那在有解的情况下,最优解的长度就是gcd(s.size(),t.size()),如果我们能循环以它的约数为长度的字符串,自然也能循环以它为长度的字符串。
证明不周,详细证明看leetcode 1071. 字符串的最大公因子
因此优化后的代码:

#include <iostream>
#include <string>
#include <algorithm>

using namespace std;

typedef long long ll;

int gcd(int a, int b) {
    while (b) {
        a %= b;
        swap(a, b);
    }
    return a;
}

string gcd_str(string s, string t) {
    if (s + t != t + s) return "";
    return s.substr(0, gcd(s.size(), t.size()));
}

int main() {
    int n;
    cin >> n;
    while (n--) {
        string s, t, gcd_st, res = "";
        cin >> s >> t;
        gcd_st = gcd_str(s, t);
        if (gcd_st == "") {
            puts("-1");
            continue;
        }
        int len_gcdst = gcd_st.size();
        int len_lcmst = (s.size() / gcd_st.size()) * (t.size() / gcd_st.size());

        for (int i = 0; i < len_lcmst; i++) {
            res += gcd_st;
        }
        cout << res << endl;
    }

    return 0;
}

C. No More Inversions

链接:C题链接

题目大意:

给定一个序列a:1,2,3,...,k1,k,k1,k2,...k(nk), 要求构造一个长度为k的排列p,然后通过p构造出b[i]=p[a[i]],要求b中的逆序对数量不得多于a中逆序对的数量,并且要求b的字典序在所有合法方案中最大。

思路

当时没看懂,今天下午又肝了一下午,看了很多博客才懂了。
首先b[i]=p[a[i]]出发,其实就是a数组用来做p的下标了,并且若p数组中是有序的,那么映射后的b数组是跟a数组一模一样的。
例如:a[]=1,2,3,4,5,4,3, p[]=1,2,3,4,5, 那么b[]=1,2,3,4,5,4,3
若p数组中有两个数调换位置,那么相应的b数组中对应的数也交换,例如p[]=1,2,3,5,4,那么b数组中的4和5也相应的发生对换,变成b[]=1,2,3,5,4,5,3,很神奇。
然后就再找规律发现,我们通过p数组改变b数组的时候,动的如果是a数组对称的那部分,那么逆序对的数量是不会发生变化的,例如上边对称的部分就是3,4,5,4,3,若改变p使得p变为p[]=1,2,4,3,5, 则对称部分就变成了就变成了4,3,5,3,4,逆序对的数量仍然没有变化,若改变了前面不对称的部分,那么逆序对就会比a
最后我们就可以在上边的基础上让b的字典序尽量的大,那么就把p的大数换到对称部分的最前边即可p[]=1,2,5,4,3,此时b[]=1,2,5,4,3,4,5
至此,solve it!

代码:

#include <iostream>

using namespace std;

int main() {
    int t;
    cin >> t;
    while (t--) {
        int n, k;
        cin >> n >> k;
        int t1 = n - k;
        int t2 = k - t1 - 1;
        for (int i = 1; i <= t2; i++) {
            cout << i << " ";
        }
        for (int i = k; i > t2; i--) {
            cout << i << " ";
        }
        cout << endl;
    }

    return 0;
}

D. Program

链接:D题链接

题目大意:

x的初值为0,给定一个字符串由+,组成,分别使x加减1,加下来由m次操作,每次给定lr,表示将lr(包括lr端点)长度的+操作屏蔽,求屏蔽后x会有多少个不同的值。

思路

首先明白一点,对于这个题而言,因为一次操作x只能变化1,因此一个区间内x取得的最大值与最小值之差加1,那么就是x所取得的不同的值,因此我们需要维护一下这个最大值与最小值。
然后访问的时候,删除lr之间的操作后,也就意味着我们不进行这段操作,那我们就需要对上边维护的值进行弥补,那么怎么进行弥补呢,就是lr期间x变化的值加到后边,再判断一种很简单的理解方法就是,对于x的变化画成一个一个图,中间切开,然后把两边接起来,看下图,中间蓝色切开,再接起来。

然后就变成了这样。

那什么叫"弥补"呢?就是原来蓝色区间中x的值是上升了,但是若删除蓝色区间操作,那么x的就直接把后边的的折线拉下来了,即x没有了那段上升操作,即从前一段的重点到后一段的起点,因此,对于前段,不管怎么删除都不受影响,后段会受影响,因此维护最值得时候对于后段给给予"补偿"即可,即后段会减去x的变化,因此对于前段,我们从前往后维护,对于后段从后往前维护即可,最后最大值就是前段与后段接起来后的最大值,同理也求得最小值,最后作差 + 1得到x的变化。

代码

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 2e5 + 10;
char s[N];
int maxpre[N], minpre[N], maxtail[N], mintail[N];
int a[N], sum[N];

int main() {
    int t;
    cin >> t;
    while (t--) {
        int n, m;
        cin >> n >> m;
        cin >> s + 1;
        for (int i = 1; i <= n; i++) {
            if (s[i] == '+') {
                a[i] = 1;
            } else {
                a[i] = -1;
            }
            sum[i] = sum[i - 1] + a[i];
            maxpre[i] = max(maxpre[i - 1], sum[i]);
            minpre[i] = min(minpre[i - 1], sum[i]);
        }
        maxtail[n + 1] = -1e9, mintail[n + 1] = 1e9;
        for (int i = n; i >= 1; i--) {
            maxtail[i] = max(maxtail[i + 1], sum[i]);
            mintail[i] = min(mintail[i + 1], sum[i]);
        }
        for (int i = 0; i < m; i++) {
            int l, r;
            cin >> l >> r;
            int temp = sum[r] - sum[l - 1];
            int maxN = max(maxpre[l-1], maxtail[r + 1] - temp);
            int minN = min(minpre[l-1], mintail[r + 1] - temp);
            cout << maxN - minN + 1 << endl;
        }
    }
    system("pause");
    return 0;
}
posted @   Xxaj5  阅读(169)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端
点击右上角即可分享
微信分享提示