练习001

发布于:2025-05-01 ⋅ 阅读:(53) ⋅ 点赞:(0)

目录

前言

数字诗意

分析

代码

封闭图形个数

分析

代码

回文数组

分析

代码

商品库存管理

分析

代码

挖矿

分析

代码

回文字符串

分析

代码


前言

好久不更新了,今天来更新一下。

当然不是主包偷懒啊,是最近的事情实在是有点多QAQ。

还有就是主包目前已经大二了,计划着暑假或者大三上去实习,不过就目前的进度而言肯定是做不到的,本人的精力也有限,所以主包决定在以后的学习中改变侧重点,不再将算法学习作为重点,而是将重点转向c++后端的技术栈上,所以今后可能算法方面的更新会比较少。

数字诗意

分析

题目巴拉巴拉说了一大堆,总之就是让我们判断一个数字能否拆解成至少两个连续的正整数相加的形式,注意这里是正整数。(今年的蓝桥C++B第一道编程题和这道题类似,只是改成了连续的整数。)

那么这道题要如何来想呢?第一印象它可能是一个找规律的题目,所以我们去打表找规律,打表方法呢就是枚举1 ~ 100内的所有整数,随后去判断其能否被组成,打表代码如下:

#include<iostream>
using namespace std;
​
bool check(int x)
{
    for(int i = 2; i <= x; i++)
        for(int j = 1; j <= x; j++)
        {
            int y = 0;
            for(int k = j; k <= j + i - 1; k++)
                y += k;
            if(y == x)
                return true;
        }
    return false;
}
​
int main()
{
    for(int i = 0; i <= 100; i++)
    {
        if(check(i)) cout << i << ' ';
        else cout << "  ";
    } 
}

从最后的运行结果可以看出只要是2^n的数字都无法组成。 这道题目只需要统计一下为2^n的数字的个数即可。

那么这道题应该如何证明呢?

首先,假设有k个数字,首项为a,则:

a + a + 1 + a + 2 + ... + a + k - 1

s = (2a + k - 1) * k / 2

所以:2s = (2a + k - 1) * k

因为:k >= 2 && a >= 1

所以:2a >= 2, k - 1 >= 1

2a + k - 1 >= 3,并且k2a + k - 1必定为一个奇数项和一个偶数项,所以2s中必定存在大于等于3的奇数因子,即:s中必定存在大于1的奇数因子,而2^n不满足这个条件,所以2^n必定是答案。

代码

#include<iostream>
typedef long long LL;
using namespace std;
int n;
LL x;
int l;
LL lowbit(LL x)
{
    return x & -x;
}
int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        cin >> x;
        if(x - lowbit(x) == 0) l++;
    }
    cout << l;
    return 0;
}

封闭图形个数

分析

题目很简单,对每个数位的封闭个数打表,随后排序便好,时间复杂度为:9 * n * logn极限情况下时间复杂度为3e7,能够通过。

代码

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 500010;
int st[] = {1, 0, 0, 0, 1, 0, 1, 0, 2, 1};
​
struct a
{
    int num, val;
    bool operator<(a& x)
    {
        if(val == x.val)
            return num < x.num;
        return val < x.val;
    }
};
​
int n;
a nums[N]; 
int get_val(int x)
{
    int s = 0;
    while(x)
    {
        s += st[x % 10];
        x /= 10;
    }
    return s;
}
​
void score()
{
    cin >> n;
    for(int i = 0; i < n; i++)
    {
        int x; cin >> x;
        nums[i] = {x, get_val(x)};
    }
    sort(nums, nums + n);
    for(int i = 0; i < n; i++)
        cout << nums[i].num << ' ';
}
​
int main()
{
    score();
} 

回文数组

分析

贪心。

根据回文数组的性质——a[i] = a[n - i + 1],我们将in - i + 1看作一对。

要对原数组进行加减操作,而无论我们对左端还是右端加减,最终的最优解都是相同的,贪心次数为abs(a[i], a[n - i + 1]

所以第一步贪心为对每对数字进行abs(a[i] - a[n - i + 1])次操作,又因为每次对指定位置进行操作时都可以顺便对后面的部分进行操作,所以筛选出可以顺便减少后面一位的情况。第一步贪心为对每一位进行操作时都将影响选择性的映射到后面一位。

代码

// 因为操作都是相对的,所以我们只对一半进行操作
#include<iostream>
using namespace std;
const int N = 100010;
typedef long long LL;
int a[N];
int n;
LL l;
int main()
{
    cin >> n;
    for(int i = 0; i < n; i++) cin >> a[i];
    for(int i = 0, j = n - 1; i <= j; i++, j--)
        a[i] -= a[j]; //计算相对数值
    
    for(int i = 0; i < n / 2; i++)
    {
        l += abs(a[i]);
        if((LL)a[i] * a[i + 1] > 0)
        {
            if(abs(a[i + 1]) > abs(a[i])) a[i + 1] -= a[i];
            else a[i + 1] = 0;
        }
    }
    cout << l;
    return 0;
}

商品库存管理

分析

乍一看还以为是一道树状数组的题目,但是仔细读题后发现并不是这么回事。

分析题目,要求如果某个操作未被执行,那么最终会有多少种商品的库存量为0。

而每次操作只会对区间上的所有商品进行+1的操作,所以我们每次操作的影响,其实就是区间上0的个数。

反过来,如果不执行此操作,影响就为区间上1的个数。

如何来写呢?

首先,我们要求出操作后的数组。这里考虑使用差分 + 前缀和处理

紧接着,我们再次进行前缀和,统计每个前缀上1的数量。

最后直接输出即可。

代码

// 每次读取的时候用 区间内1的个数
#include<iostream>
using namespace std;
typedef long long LL;
const int N = 300010;
int n, m;
int a[N];
int l;
int num[N][2];
​
void score()
{
    cin >> n >> m;
    for(int i = 1; i <= m; i++)
    {
        scanf("%d%d", num[i], num[i] + 1);
        a[num[i][0]]++; a[num[i][1] + 1]--;
    }
    for(int i = 1; i <= n; i++)
        a[i] += a[i - 1]; //计算出原区间
    for(int i = 1; i <= n; i++) //第二次前缀和统计区间内1的数量
    {
        if(a[i] == 0) l++; //统计0的数量
        if(a[i] == 1) a[i] = a[i - 1] + 1; //统计了一的数量
        else a[i] = a[i - 1]; //第二次前缀和
    }
    for(int i = 1; i <= m; i++) // 总共的数量 + 区间内1的个数
        printf("%d\n", l + (a[num[i][1]] - a[num[i][0] - 1]));
}
​
int main()
{
    score();
    return 0;
}

挖矿

分析

要求移动距离为m能够获得的最多的矿石,发现每次可以选择向左或者向右走。

首先根据贪心,想到不会反复的左右移动,但是可以折返。即:一个位置至多只会被经过两次,并且只可以改变方向一次。

所以我们可以将数轴分为两个部分,一边是到达过两次的,另一边是到达过一次的。

所以对于本道题我们两部分的长度即可,时间复杂度为O(m)

代码

// 2 * x + y = m
#include<iostream>
using namespace std;
const int N = 4e6 + 10, ZERO = 2e6 + 4;
int n, m;
int s[N];
int bd_max = 0;
int main()
{
    cin >> n >> m;
    while(n--)
    { 
        int x;cin >> x; s[ZERO + x] += 1;
    }
    for(int i = 1; i < N; i++)
        s[i] += s[i - 1];
    for(int i = 0; i <= m / 2; i++)
    {
        
        bd_max = max(bd_max, s[ZERO + i] - s[ZERO - (m - 2 * i) - 1]);
        bd_max = max(bd_max, s[ZERO + (m - 2 * i)] - s[ZERO - i - 1]);
    }
    cout << bd_max;
    return 0;
}

回文字符串

分析

发现只可以对字符串的开头加上指定的任意数量的字母。

首先很容易想到如果非指定字符的部分如果不是回文串的话,那么一定无法构成字符串。

紧接着想到如果中间非指定字符部分是回文串但两端的较短的一部分与较长的那一部分对应的部分不是回文串的话也无法组成回文串。

所以本道题的思路就是:先找出中间非指定字符的部分,判断其是否是回文串,随后向两边扩展,查看是否能够到达边界。

若可以到达边界也要区分左边界和右边界,因为我们只能对左边界进行操作,所以对于到达右边界的情况依旧无法组成回文串。

代码

#include<iostream>
using namespace std;
string s;
​
bool check(int l, int r)
{
    if(r == -1) return true;
    int i = (l + r) / 2, j = (l + r + 1) / 2; // 上取整和下取整
    while(true)
    {
        if(i == -1) return true;
        if(j == s.size()) return false;
        if(s[i] == s[j]) i--, j++;
        else return false;
    }
    return true;
}
​
void score()
{
    cin >> s;
    int l = s.size(), r = -1;
    for(int i = 0; i < s.size(); i++)
    {
        if(s[i] != 'l' && s[i] != 'q' && s[i] != 'b')
        {
            l = min(l, i);
            r = max(r, i);
            //cout << r;
        }
    }
    if(check(l, r)) puts("Yes");
    else puts("No");
    
}
int main()
{
    int t;
    cin >> t;
    while(t--) score();
    return 0;
}

网站公告

今日签到

点亮在社区的每一天
去签到