刷爆历年蓝桥杯真题--第十四届

C++A组


平方差

AcWing 4996. 平方差

找规律 O ( 1 ) O(1) O(1)

数学问题,答案肯定满足某种规律,简单的规律可以通过前几项直接看出来,复杂的需要推导 数学问题,答案肯定满足某种规律,简单的规律可以通过前几项直接看出来,复杂的需要推导 数学问题,答案肯定满足某种规律,简单的规律可以通过前几项直接看出来,复杂的需要推导

/*
    实在看不出来就打表
*/
#include <iostream>
#include <set>

using namespace std;
set<int> a;

int main(){
    for (int i = 0; i <= 10; i ++){
        for (int j = 0; j <= 10; j ++){
            int t = i * i - j * j;
            if (t > 0) a.insert(t);
        }
    }
    
    for (auto i : a){
        printf("%d ", i);
    }
    return 0;
}

output:

1 3 4 5 7 8 9 11 12 13 15 16 17 19 20 21 24 25 27 28 32 33 35 36 39 40 45 48 49 51 55 56 60 63 64 65 72 75 77 80 81 84 91 96 99 100 

可以看出一定包含奇数项,同时也包含偶数项,但是偶数项都是 4 的倍数 用一下前缀和的思想 可以看出一定包含奇数项,同时也包含偶数项,但是偶数项都是4的倍数\\ 用一下前缀和的思想 可以看出一定包含奇数项,同时也包含偶数项,但是偶数项都是4的倍数用一下前缀和的思想

#include <cstdio>

using namespace std;

int l, r;

int getcnt(int x){
    return (x + 1) / 2 + x / 4;
}

int main(){
    scanf("%d%d", &l, &r);
    printf("%d\n", getcnt(r) - getcnt(l - 1));
    return 0;
}

更小的数

AcWing. 4997 更小的数

区间dp O ( n 2 ) O(n^2) O(n2)

/*
    f[i][j]表示区间i, j是否是一个可翻转序列
    状态转移 状态[i, j]的状态可以有[i + 1, j - 1]转移过来
*/
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 5010;

bool f[N][N];
char str[N];
int n;

int main(){
    scanf("%s", str);
    n = strlen(str);
    
    int res = 0;
    for (int len = 2; len <= n; len ++)
        for (int l = 0; len + l - 1 < n; l ++){
            int r = len + l - 1;
            if (str[l] > str[r]) f[l][r] = true;
            else if (str[l] == str[r] && f[l + 1][r - 1]) f[l][r] = true;
            else f[l][r] = false;
            if (f[l][r]) res ++;
        }
    printf("%d\n", res);
    
    return 0;
}

颜色平衡树

AcWing. 4998 颜色平衡树


买瓜

AcWing. 4999 买瓜

折半搜索+哈希 O ( 2 n / 2 ) O(2^{n/2}) O(2n/2)

首先每个瓜会被劈成两半,所以为了不出现 0.5 的情况 现将每个瓜和目标瓜的重量乘 2 ,这样每个瓜就有三种选择, { 0 → 不选这个瓜 1 → 选这个瓜,并将其劈成两半 2 → 选这一整个瓜 首先每个瓜会被劈成两半,所以为了不出现0.5的情况\\ 现将每个瓜和目标瓜的重量乘2,这样每个瓜就有三种选择,\\ \begin{cases} 0 \rightarrow 不选这个瓜\\ 1 \rightarrow 选这个瓜,并将其劈成两半\\ 2 \rightarrow 选这一整个瓜 \end{cases} 首先每个瓜会被劈成两半,所以为了不出现0.5的情况现将每个瓜和目标瓜的重量乘2,这样每个瓜就有三种选择, 0不选这个瓜1选这个瓜,并将其劈成两半2选这一整个瓜

这样直接用 d f s 爆搜的时间复杂度为 O ( 3 n ) = 1 0 14 铁超时, 采用折半搜索后的时间复杂度为 O ( 3 n / 2 ) = 1 0 8 由于 d f s 过程中有剪枝过程所以实际效率低于 3 n / 2 这样直接用dfs爆搜的时间复杂度为O(3^n) = 10^{14}铁超时,\\ 采用折半搜索后的时间复杂度为O(3^{n / 2}) = 10^8\\ 由于dfs过程中有剪枝过程所以实际效率低于3^{n/2} 这样直接用dfs爆搜的时间复杂度为O(3n)=1014铁超时,采用折半搜索后的时间复杂度为O(3n/2)=108由于dfs过程中有剪枝过程所以实际效率低于3n/2

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
typedef pair<int, int> PII;
typedef long long LL;

const int N = 31, M = 1e7 + 7;
const int null = 2e9 + 7;

int w[N];
int n, m;
int ans = 100;
PII h[M];

int find(int x){
    int key = x % M;
    while (h[key].first != x && h[key].first != null){
        key ++;
        if (key == M) key = 0;
    }
    return key;
}

void dfs_1(int u, int len, LL s, int cnt){
    if (s > m) return;
    if (u == len){
        int key = find(s);
        h[key] = {s, min(h[key].second, cnt)};
        return;
    }
    dfs_1(u + 1, len, s, cnt);
    dfs_1(u + 1, len, s + w[u], cnt);
    dfs_1(u + 1, len, s + w[u] / 2, cnt + 1);
}

void dfs_2(int u, int len, LL s, int cnt){
    if (s > m || cnt >= ans) return;
    if (u == len){
        int key = find(m - s);
        if (key != null)
            ans = min(ans, h[key].second + cnt);
        return;
    }
    
    dfs_2(u + 1, len, s, cnt);
    dfs_2(u + 1, len, s + w[u], cnt);
    dfs_2(u + 1, len, s + w[u] / 2, cnt + 1);
}

int main(){
    scanf("%d%d", &n, &m);
    m *= 2;
    
    for (int i = 0; i < n; i ++){
        scanf("%d", &w[i]), w[i] *= 2;
    }
    sort(w, w + n, greater<int>());
    
    for (int i = 1; i < M; i ++)
        h[i] = {null, null};
        
    dfs_1(0, n / 2 - 1, 0, 0);//折半也卡死,必须前移一项,粪数据:(
    dfs_2(n / 2 - 1, n, 0, 0);
    
    if (ans == 100) puts("-1");
    else printf("%d\n", ans);
    return 0;
}

网络稳定性

AcWing. 5000 网络稳定性

最大生成树+LCA O ( n l o g n ) O(nlogn) O(nlogn)

一条路的稳定度是整条路径中权重最小的那个,一条两点之间的通信稳定度是所有路径稳定度最高的那个 因此我们要找两点之间的权重最大的边组成的路径,而最终表示这条路径的稳定度是其中的最小边权 找到两点之间的最大版权用最大生成树 K r u s k a l , O ( m l o g m ) 可以实现预处理出来 找两点之间的最小权重, d f s 的话 O ( n ) 加上询问的话就是 O ( q n ) = O ( n 2 ) 超时 需要优化找最下边权的操作,这里采用 L C A 处理节点到根节点的最短边权 建 L C A   O ( n l o g n ) 提前预处理查询 O ( l o g n ) 一条路的稳定度是整条路径中权重最小的那个,一条两点之间的通信稳定度是所有路径稳定度最高的那个\\ 因此我们要找两点之间的权重最大的边组成的路径,而最终表示这条路径的稳定度是其中的最小边权\\ 找到两点之间的最大版权用最大生成树Kruskal, O(mlogm)可以实现预处理出来\\ 找两点之间的最小权重,dfs的话O(n)加上询问的话就是O(qn) = O(n^2)超时\\ 需要优化找最下边权的操作,这里采用LCA处理节点到根节点的最短边权\\ 建LCA \ O(nlogn)提前预处理 查询O(logn) 一条路的稳定度是整条路径中权重最小的那个,一条两点之间的通信稳定度是所有路径稳定度最高的那个因此我们要找两点之间的权重最大的边组成的路径,而最终表示这条路径的稳定度是其中的最小边权找到两点之间的最大版权用最大生成树Kruskal,O(mlogm)可以实现预处理出来找两点之间的最小权重,dfs的话O(n)加上询问的话就是O(qn)=O(n2)超时需要优化找最下边权的操作,这里采用LCA处理节点到根节点的最短边权LCA O(nlogn)提前预处理查询O(logn)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;
typedef pair<int, int> PII;

const int N = 100010, M = 2 * N;

struct Edge{
    int a, b, c;
    bool operator< (const Edge& o) const{
        return c > o.c;
    }
}edge[3 * N];
int p[N]; 
int n, m, q;
int h[N], e[M], w[M], ne[M], idx;
int depth[N], f[N][16], dp[N][16];//dp[i][j]表示从i往上条2^j (不含i + 2^j)步内的最小边

void add(int a, int b, int c){
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

int find(int x){
    if (x != p[x]) p[x] = find(p[x]);
    return p[x];
}

void kruskal(){
    sort(edge, edge + m);
    for (int i = 0; i <= n; i ++) p[i] = i;
    
    memset(h, -1, sizeof h);
    for (int i = 0; i < m; i ++){
        int a = edge[i].a, b = edge[i].b, c = edge[i].c;
        int fa = find(a), fb = find(b);
        if (fa != fb){
            add(a, b, c); add(b, a, c);
            p[fa] = fb;
        }
    }
}

void bfs(int root){
    memset(depth, 0x3f, sizeof depth);
    queue<int> q;
    q.push(root);
    depth[root] = 1; depth[0] = 0;
    
    while (q.size()){
        auto t = q.front();
        q.pop();
        
        for (int i = h[t]; ~i; i = ne[i]){
            int j = e[i];
            
            if (depth[j] > depth[t] + 1){
                depth[j] = depth[t] + 1;
                f[j][0] = t;
                dp[j][0] = w[i];

                q.push(j);
                
                for (int k = 1; k <= 15; k ++){
                    f[j][k] = f[f[j][k - 1]][k - 1];
                    dp[j][k] = min(dp[j][k - 1], dp[f[j][k - 1]][k - 1]);
                }
            }
        }
    }
}

int lca(int a, int b){
    int res = 0x3f3f3f3f;
    if (depth[a] < depth[b]) return lca(b, a);
    
    for (int i = 15; i >= 0; i --){//从大到小枚举,先比较二进制高位
        if (depth[f[a][i]] >= depth[b]){
            res = min(res, dp[a][i]);
            a = f[a][i];
        }
    }
    if (a == b) return res;
    
    for (int i = 15; i >= 0; i --){
        if (f[a][i] != f[b][i]){
            res = min(res, min(dp[a][i], dp[b][i]));
            a = f[a][i];
            b = f[b][i];
        }
    }
    return min(res, min(dp[a][0], dp[b][0]));
}

int main(){
    scanf("%d%d%d", &n, &m, &q);
    
    for (int i = 0; i < m; i ++){
        scanf("%d%d%d", &edge[i].a, &edge[i].b, &edge[i].c);
    }
    kruskal();
    bfs(e[0]);

    while (q --){
        int a, b;
        scanf("%d%d", &a, &b);
        int res = lca(a, b);
        if (res > 0) printf("%d\n", res);
        else puts("-1");
    }
    
    return 0;
}

异或和之和

AcWing. 5001异或和之和

前缀和+按位贡献 O ( n ) O(n) O(n)

由于要求一段区间的异或值,可以采用前缀和事先枚举前缀的异或和, 然后枚举每一段区间求其异或和 O ( n 2 ) 最简单能拿 60 % 分 由于要求一段区间的异或值,可以采用前缀和事先枚举前缀的异或和,\\ 然后枚举每一段区间求其异或和O(n^2) 最简单能拿60\%分 由于要求一段区间的异或值,可以采用前缀和事先枚举前缀的异或和,然后枚举每一段区间求其异或和O(n2)最简单能拿60%

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100010;

int a[N], s[N];
int n;

int main(){
    scanf("%d", &n);
    
    for (int i = 1; i <= n; i ++){
        scanf("%d", &a[i]);
        s[i] = s[i - 1] ^ a[i];
    }
    
    int res = 0;
    for (int i = 1; i <= n; i ++){
        for (int j = 0; j < i; j ++){
            res += s[i] ^ s[j];
        }
    }
    printf("%d\n", res);
    return 0;
}

优化采用二进制拆位贡献法,每个数一共有 20 位,只需要考虑,每个数每一位对结果的贡献即可 例如:此题中实际利用了三位,模拟一下: a [ i ]          s [ i ] 1 : 001     001 2 : 010     011 3 : 011     000 4 : 100     100 5 : 101     001 由于异或和前缀和能更好的理解,所以还是用上异或前缀和 当前位为 1 ,表示这个数当前位在前缀和中有贡献,当前位为 0 表示当前数这个位在前缀和没有贡献 这样我们就可以找有贡献的区间了,同理要看这个数在 [ L + 1 , R ] 之间是否有贡献,就可以用 s [ L ] ⊕ s [ R ] ,如果为 1 就有贡献 这样在求每一位可以影响哪些区间时就是找这个数当前位前面不同数 ( 1 时找 0 , 0 时找 1 )的个数 , s [ 0 ] 也要考虑在内,表示贡献整个前缀和 例如 i = 0 , j = 5 时可以影响的区间是 ( 0 , 5 ] , ( 3 , 5 ] , ( 4 , 5 ] i = 0 , j = 4 时可以影响的区间是 ( 1 , 4 ] , ( 2 , 4 ] 优化采用二进制拆位贡献法,每个数一共有20位,只需要考虑,每个数每一位对结果的贡献即可\\ 例如:此题中实际利用了三位,模拟一下:\\ a[i]\ \ \ \ \ \ \ \ s[i]\\ 1: 001 \ \ \ 001\\ 2:010 \ \ \ 011\\ 3:011 \ \ \ 000\\ 4:100 \ \ \ 100\\ 5: 101 \ \ \ 001\\ 由于异或和前缀和能更好的理解,所以还是用上异或前缀和\\ 当前位为1,表示这个数当前位在前缀和中有贡献,当前位为0表示当前数这个位在前缀和没有贡献\\ 这样我们就可以找有贡献的区间了,同理要看这个数在[L + 1, R]之间是否有贡献,就可以用s[L] \oplus s[R],如果为1就有贡献\\ 这样在求每一位可以影响哪些区间时就是找这个数当前位前面不同数(1时找0,0时找1)的个数, s[0]也要考虑在内,表示贡献整个前缀和\\ 例如i = 0,j=5时可以影响的区间是(0, 5], (3, 5], (4, 5]\\ i=0, j =4时可以影响的区间是(1, 4], (2, 4] 优化采用二进制拆位贡献法,每个数一共有20位,只需要考虑,每个数每一位对结果的贡献即可例如:此题中实际利用了三位,模拟一下:a[i]        s[i]1:001   0012:010   0113:011   0004:100   1005:101   001由于异或和前缀和能更好的理解,所以还是用上异或前缀和当前位为1,表示这个数当前位在前缀和中有贡献,当前位为0表示当前数这个位在前缀和没有贡献这样我们就可以找有贡献的区间了,同理要看这个数在[L+1,R]之间是否有贡献,就可以用s[L]s[R],如果为1就有贡献这样在求每一位可以影响哪些区间时就是找这个数当前位前面不同数(1时找0,0时找1)的个数,s[0]也要考虑在内,表示贡献整个前缀和例如i=0j=5时可以影响的区间是(0,5],(3,5],(4,5]i=0,j=4时可以影响的区间是(1,4],(2,4]

#include <cstdio>
#include <cstring>
#include <algorithm>
#define int long long

using namespace std;

const int N = 100010;

int a[N], s[N];
int n;

signed main(){
    scanf("%lld", &n);
    
    for (int i = 1; i <= n; i ++){
        scanf("%lld", &a[i]);
        s[i] = s[i - 1] ^ a[i];
    }
    
    int res = 0;
    //当取2^20时,等于1 << 20, 一共21位
    for (int i = 20; i >= 0; i --){
        int n1 = 0, n0 = 1; //n1表示前面1的个数就是s[L]==1的情况,n0表示0的个数,包含s[0]
        for (int j = 1; j <= n; j ++){
            if ((s[j] >> i) % 2){
                res += n0 * (1 << i);
                n1 ++;
            }
            else{
                res += n1 * (1 << i);
                n0 ++;
            }
        }
    }
    printf("%lld\n", res);
    return 0;
}

像素放置

AcWing. 5002像素放置

方法

to be cotinued


翻转硬币

AcWing. 5003

莫比乌斯函数+筛质数


未完待续~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ˇasushiro

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值