长沙学院2023 第三次蓝桥杯训练题解

本场题目和题解由邱一凡学长和我一同给出。另附上邱一凡学长的博客:小白猿@

删除字符(贪心)

链接:删除字符

代码

#include 
#include 
using namespace std;
const int N = 110;
char s[N];

int main()
{
  int t;
  cin >> (s + 1) >> t;

  int n = strlen(s + 1);
  string ans = "";
  for (int i = 1; i <= n; i ++) {
    char c = 'Z'; // 初始化最大
    for (int j = 0; i + j <= n && j <= t; j ++ ) { // [i, i + t]段找最小字符,这些数才能通过删除最多t个字符到达前面去
      c = min(c, s[i + j]);
    }

    int k = i;  // 记录最小字符的下标
    for (int j = 0; i + j <= n && j <= t; j ++ ) {  // 找最小字符所在的下标
      if (c == s[i + j]) {
        k = i + j;
        break;
      }
    }
 
    ans += c;
    t -= (k - i);
    i = k;
  }

  for (int i = 0; i < ans.size() - t; i ++ ) {
    cout << ans[i];
  }
  return 0;
}

美丽的区间(二分 / 双指针)

题目好像需要vip了,暂时先看看题目和代码吧。
链接:美丽的区间

题解

根据题意可得,答案具有两段性(即至少存在一个大于等于答案的长度一定是美丽区间,小于答案的长度一定不是美丽区间)。因此可以直接二分,对于二分出来的值 m i d mid mid, 如果存在一个i,使得区间 [ i − m i d + 1 , i ] [i - mid + 1, i] [imid+1,i] 的和大于等于 s s s,那么这个一定是一个美丽区间,返回此长度可行。

代码(二分)

#include 

using namespace std;
const int N = 1e5 + 10;
int sum[N]; // 前缀和数组
int n, s;

bool check(int mid)
{
    for (int i = mid; i <= n; i ++ ) {
        if (sum[i] - sum[i - mid] >= s) return true; // 前缀和O(1)得到区间[i, i - mid + 1]的和
    }
    return false;
}

int main()
{
    cin >> n >> s;

    for (int i = 1; i <= n; i ++ ) {
        int x;
        cin >> x;
        sum[i] = sum[i - 1] + x; // 求前缀和
    }

    int l = 1, r = n + 1;
    while (l < r) {
        int mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    if (l == n + 1) l = 0; // 说明整个区间的和都小于s,无解
    cout << l << endl;
    return 0;
}

代码(双指针)

#include 

using namespace std;
const int N = 1e5 + 10;
int sum[N];
int n, s;

int main()
{
    cin >> n >> s;

    for (int i = 1; i <= n; i ++ ) {
        int x;
        cin >> x;
        sum[i] = sum[i - 1] + x;
    }

    int ans = n + 1; // 定义一个如果存在合法的答案就会被替换的值
    for (int l = 1, r = 1; l <= n; l ++ ) {
        while (r < n && sum[r] - sum[l - 1] < s) r ++;
        if (sum[r] - sum[l - 1] >= s) ans = min(ans, r - l + 1);
    }
    if (ans == n + 1) ans = 0;  // 长度为n + 1的不存在
    cout << ans << endl;
    return 0;
}

XOR Mixup(二进制)

链接:XOR Mixup

代码

// XOR Mixup https://codeforces.com/problemset/problem/1698/A
/*
异或和性质:
1.a[i] ^ a[i] = a[i]
2.a[i] ^ 0 = a[i]
设sum = ai ^ a_{i + 1} ^ …… ^ a_n
我们求所有数的异或和sum后,用sum ^ ai 根据性质1,将原式中存在ai还原成0,再根据性质二就得到了
除ai以外所有数的异或和,判断是否和ai相同即可
*/
#include 
using namespace std;
const int N = 110;

int n, a[N];
void solve(){
    cin >> n;
    int XOR_sum = 0;
    for (int i = 1; i <= n; i ++) {
        cin >> a[i];
        XOR_sum ^= a[i];
    }

    for (int i = 1; i <= n; i ++) {
        if (XOR_sum ^ a[i] == a[i]) {
            cout << a[i] << "\n";
            return ;
        }
    }
}

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

P8809 [蓝桥杯 2022 国 C] 近似 GCD(数论 + 双指针 / 动态维护)

链接:P8809 [蓝桥杯 2022 国 C] 近似 GCD

代码(双指针)

#include 
#define endl '\n'

using namespace std;

typedef pair<int, int> PII;
typedef long long ll;
const int N = 2e5 + 10;
int g[N];

void solve()
{
    int n, gd;
    cin >> n >> gd;

    for (int i = 1; i <= n; i ++ ) cin >> g[i];

    ll ans = 0;
    int cnt = 0;  // 区间里面不是g的倍数的个数
    for (int l = 1, r = 0; l <= n; l ++ ) {
        while (r < n && (!cnt || g[r + 1] % gd == 0)) {  // 判断当前r + 1是否能进入到区间
            r ++;
            if (g[r] % gd != 0) cnt ++; // 不是g的倍数数量加一
        }
        if (g[l] % gd != 0) cnt --;
        if (r - l + 1 >= 2) ans += r - l; // 不是g的倍数数量减一,因为g[l]下一次会从区间删除
    }
    cout << ans << endl;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cout.tie(nullptr);

    int __ = 1;
//     cin >> __;
    while (__ -- ) solve();
    return 0;
}

题解(动态维护)

对于每一个 a i a_i ai 动态维护两个数 l a s t 1 , l a s t 2 last1, last2 last1,last2,分别为从 i i i 开始向前的第一个和第二个 g g g 的倍数的下标,当区间内非 g g g 倍数的数的数量 < = 1 <=1 <=1 时该区间就是可行的,于是答案就是 a n s + = i − l a s t 2 − 1 ans += i - last2 - 1 ans+=ilast21

代码( 动态维护)

// P8809 [蓝桥杯 2022 国 C] https://www.luogu.com.cn/problem/P8809
#include 
using namespace std;
#define ll long long
const int N = 1e5 + 10;

int a[N];

inline int gcd(int a, int b){
    if(b) while( (a %= b) && (b %= a));
    return a + b;
}

int main(){
    int n, g;
    cin >> n >> g;

    for (int i = 1; i <= n; i ++) cin >> a[i];
    
    int last1 = 0, last2 = 0; // last1:从i向前数第一个非g的倍数下标,last2:第二个非g的倍数的下标
    ll ans = 0;
    for (int i = 1; i <= n; i ++) {
        if(a[i] % g != 0) { // 更新
            last2 = last1;
            last1 = i;
        }
        ans += i - last2 - 1; // 我可以将last1所在的数改成g,那么从[j, i] (last2 + 1 <= j <= i)肯定都是合法的,-1是减去长度为1的区间
        // 关于gcd是g的倍数情况,我们也可以将某一个数直接改为g达到要求
    }

    cout << ans;
    return 0;
}

修改数组(并查集 / 双指针)

修改数组

题解(STL + 二分)

转换为 有一个有所有正整数的集合,每次拿一个大于等于当前数的数出来,恰好符合二分每次找 > = >= >= 当前数的第一个数。每次找到后从 set 中删除即可。

代码(STL + 二分)

//修改数组 https://www.lanqiao.cn/problems/185/learning/?page=8&first_category_id=1&sort=students_count
#include 
using namespace std;

const int N = 1e6 + 10 + 1e5, MAX = 1100000;

set<int>s;

int main(){
    int n;
    cin >> n;
    for (int i = 1; i <= MAX; i ++){
        s.insert(i); // 将此时没有使用过的数存入set
    }

    for (int i = 1; i <= n; i ++) {
        int x; cin >> x;
        auto it = s.lower_bound(x); // 每次二分找大于等于当前ai的数
        cout << (*it) << " ";
        s.erase(it); // 将已经使用过的数丢弃
    }
    return 0;
}

代码(并查集)

//修改数组 https://www.lanqiao.cn/problems/185/learning/?page=8&first_category_id=1&sort=students_count
#include 
using namespace std;
const int N = 1e6 + 100010, MAX = 1100000;

int p[N];

int get(int x){ // 压缩路径
    if(x != p[x]) p[x] = get(p[x]);
    return p[x];
}

int main(){
    int n;
    cin >> n;
    for (int i = 1; i <= MAX; i ++) p[i] = i; // 并查集,每个数都指向大于等于自己的第一个数

    for (int i = 1; i <= n; i ++) {
        int x; cin >> x;
        int r = get(x);
        p[r] = r + 1; // 当前找到的数被使用,于是要指向该数的下一个
        cout << r << " ";
    }
    return 0;
}

你可能感兴趣的:(蓝桥杯,c++,算法)