蓝桥杯C++大学B组一个月冲刺记录2024/3/24

蓝桥杯C++大学B组一个月冲刺记录2024/3/23

规则:每日三题

今日还是没有完成既定任务(6/9):D

1.滑动窗口

给定一个大小为 n≤106的数组。
有一个大小为 k的滑动窗口,它从数组的最左边移动到最右边。
你只能在窗口中看到 k 个数字。
每次滑动窗口向右移动一个位置。
以下是一个例子:
该数组为 [1 3 -1 -3 5 3 6 7],k 为 3。
窗口位置 最小值 最大值
[1 3 -1] -3 5 3 6 7 -1 3
1 [3 -1 -3] 5 3 6 7 -3 3
1 3 [-1 -3 5] 3 6 7 -3 5
1 3 -1 [-3 5 3] 6 7 -3 5
1 3 -1 -3 [5 3 6] 7 3 6
1 3 -1 -3 5 [3 6 7] 3 7
你的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。

单调队列 + 模板题
单调队列能解决的问题:给区间长度为k的区间内求其中的最大值或者最小值
(stl中双端队列deque实现,比手写数组模拟慢许多)

#include<iostream>
#include<queue>

using namespace std;

const int N = 1e6 + 10;

int p[N];
deque<int>q;

int n,k;

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

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

    for(int i = 1;i <= n;++i)
    {
        while(q.size() != 0 && q.front() < i - k + 1) q.pop_front();
        while(q.size() != 0 && p[q.back()] > p[i]) q.pop_back();
        q.push_back(i);

        if(i >= k) cout << p[q.front()] << ' ';
    }

    cout << endl;
    q.clear();

    for(int i = 1;i <= n;++i)
    {
        while(q.size() != 0 &&q.front() < i - k + 1) q.pop_front();
        while(q.size() != 0 && p[q.back()] < p[i]) q.pop_back();
        q.push_back(i);

        if(i >= k) cout << p[q.front()] << ' ';
    }


    return 0;
}

2.子矩阵

给定一个 n×m(n 行 m 列)的矩阵。
设一个矩阵的价值为其所有数中的最大值和最小值的乘积。
求给定矩阵的所有大小为 a×b(a 行 b 列)的子矩阵的价值的和。
答案可能很大,你只需要输出答案对 998244353
取模后的结果。

二维单调队列

#include<iostream>

using namespace std;

const int N = 1005;
const int mod = 998244353;

int p[N][N];
int row_max[N][N];//第i行j列元素起,长度为b的子序列长度最大值
int row_min[N][N];//第i行j列元素起,长度为b的子序列长度最小值

typedef long long LL;

int n,m,A,B;

LL ans = 0;

void work_max(int a[],int b[],int tot,int k)
{
    int h[N];
    int r = -1,l = 0;

    for(int i = 1; i <= tot; ++i){

        while(r >= l && h[l] < i - k + 1) l++;
        while(r >= l && a[h[r]] <= a[i]) r--;

        r++;
        h[r] = i;

        if(i - k + 1 >= 1) b[i - k + 1] = a[h[l]];
    }

    return;
}

void work_min(int a[],int b[],int tot,int k)
{
    int h[N];
    int r = -1,l = 0;

    for(int i = 1; i <= tot; ++i){

        while(r >= l && h[l] < i - k + 1) l++;
        while(r >= l && a[h[r]] >= a[i]) r--;

        r++;
        h[r] = i;

        if(i - k + 1 >= 1) b[i - k + 1] = a[h[l]];
    }

    return;
}
int main()
{
    cin >> n >> m >> A >> B;

    for(int i = 1;i <= n;++i){
        for(int j = 1;j <= m;++j){
            cin >> p[i][j];
        }
    }

    for(int i = 1;i <= n;++i)
    {
        work_max(p[i],row_max[i],m,B);
        work_min(p[i],row_min[i],m,B);
    }
    
    int a[N],b[N],c[N];
    for(int i = 1;i <= m - B + 1;++i)
    {
        for(int j = 1;j <= n; ++j) a[j] = row_max[j][i];
        work_max(a,b,n,A);
        for(int j = 1;j <= n; ++j) a[j] = row_min[j][i];
        work_min(a,c,n,A);
        
        for(int j = 1;j <= n - A + 1;++j) ans = (ans + (LL)b[j] * c[j])%mod;
        
    }
    
    cout << ans << endl;
    return 0;

}

3.最长子序列

输入一个长度为 n 的整数序列,从中找出一段长度不超过 m
的连续子序列,使得子序列中所有数的和最大。

前缀和 + 单调队列
由于求某区间的数总和,考虑到前缀和:res[r] - res[l - 1]表示 l 到 r 区间的数总和且 r - l + 1 <= m
就可以将问题抽象成在给定 r 的情况下 在某个r之前长度为m的区间内找到一个最小值

#include<iostream>
#include<queue>

using namespace std;

typedef long long LL;

const int N = 1e6 + 10;
const LL INF = 0x3f3f3f3f3f;

int p[N];
long long res[N];

deque<int>q;

LL ans = -INF;

int n,k;

int main()
{
    cin >> n >> k;
    
    for(int i = 1;i <= n;++i)
    {
        cin >> p[i];
        res[i] = res[i - 1] + p[i];
    }
    
    q.push_back(0);
    
    for(int i = 1;i <= n;++i){
        
        while(q.size() != 0 && q.front() < i - k) q.pop_front();
        
        while(q.size() != 0 && res[q.back()] > res[i]) q.pop_back();
        
        ans = max(ans,(LL)res[i] - res[q.front()]);
        
        q.push_back(i);
        
    
    }

    cout << ans << endl;

    return 0;

}

4.烽火传递

烽火台是重要的军事防御设施,一般建在交通要道或险要处。
一旦有军情发生,则白天用浓烟,晚上有火光传递军情。
在某两个城市之间有 n 座烽火台,每个烽火台发出信号都有一定的代价。
为了使情报准确传递,在连续 m个烽火台中至少要有一个发出信号。
现在输入 n,m 和每个烽火台的代价,请计算在两城市之间准确传递情报所需花费的总代价最少为多少。

单调队列 + dp
这个题的dp转移思路和最长上升子序列的转移方式有点像。但是暴力转移会tle,所以用单调队列来优化
模型转换和上一个题思路很像

#include<iostream>

using namespace std;

const int N = 2e5 + 10;
const int INF = 0x3f3f3f3f;

int w[N];
int f[N];//需要仅考虑前i个烽火台且i点亮的方案的最小值
int h[N];
int l = 0,r = - 1;

int n,m;

int main(){

    cin >> n >> m;

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

    for(int i = 0;i <= n; ++i)
    {
        while(r >= l&& h[l] < i - m + 1) l++;
        while(r >= l&& f[h[r]] >= f[i]) r--;
        
        r++;
        h[r] = i;
        
        f[i + 1] = f[h[l]] + w[i + 1];
    }

    cout << f[n + 1] << endl;

    return 0;

}

5.网络分析

小明正在做一个网络实验。
他设置了 n 台电脑,称为节点,用于收发和存储数据。
初始时,所有节点都是独立的,不存在任何连接。
小明可以通过网线将两个节点连接起来,连接后两个节点就可以互相通信了。
两个节点如果存在网线连接,称为相邻。
小明有时会测试当时的网络,他会在某个节点发送一条信息,信息会发送到每个相邻的节点,之后这些节点又会转发到自己相邻的节点,直到所有直接或间接相邻的节点都收到了信息。
所有发送和接收的节点都会将信息存储下来。
一条信息只存储一次。
给出小明连接和测试的过程,请计算出每个节点存储信息的大小

并查集 + 路径压缩
这个题的关键在于其精妙的find函数写法。

#include<iostream>

using namespace std;

const int N = 1e4 + 10;

int f[N];
int d[N];
int n,m;

int find(int x){
    if(x == f[x] || f[f[x]] == f[x]) return f[x];
    int r = find(f[x]);
    d[x] += d[f[x]];
    f[x] = r;
    return r;
}
int main()
{
    cin >> n >> m;

    for(int i = 1;i <= n; ++i) f[i] = i;

    while(m--){
        int t,x,y;

        cin >> t >> x >> y;

        if(t == 1){
            if(x == y) continue;
            
            x = find(x),y = find(y);

            if(x!=y){
                f[x] = y;
                d[x] -= d[y];
            }
        }
        else
        {
            x = find(x);
            d[x] += y;
        }

    }

    for(int i = 1;i<=n;++i){
        if(i == find(i)) cout << d[i] << ' ';
        else cout << d[i] + d[find(i)] << ' ';
    }

    return 0;

}

6.格子游戏

Alice和Bob玩了一个古老的游戏:首先画一个 n×n 的点阵(下图 n=3 )。
接着,他们两个轮流在相邻的点之间画上红边和蓝边:
直到围成一个封闭的圈(面积不必为 1)为止,“封圈”的那个人就是赢家。因为棋盘实在是太大了,他们的游戏实在是太长了!
他们甚至在游戏中都不知道谁赢得了游戏。
于是请你写一个程序,帮助他们计算他们是否结束了游戏?

并查集 + 判环
依旧是是那个思路:当同一个连通块中的亮点连一条边的时候,产生环

#include<iostream>

using namespace std;

const int N = 1e5 + 10;

int f[N];

int n,m;

int query(int x,int y){
    return x * n + y;
}

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


int x,y;
char c;

int main(){

    cin >> n >> m;

    for(int i = 1;i < 2 * n * n; ++i) f[i] = i;

    int ans = -1;
    for(int i = 1;i <= m; ++i){

        int a,b;

        cin >> x >> y >> c;

        x--,y--;

        a = query(x,y);
        if(c == 'D') b = query(x + 1, y);
        else b = query(x, y + 1);

        int pa = find(a);
        int pb = find(b);

        if(pa == pb){
            ans = i;
            break;
        }

        f[pa] = pb;
    } 

    if(ans == -1) cout << "draw" << endl;
    else cout << ans << endl;

    return 0;
}

上一篇:使用Docker搭建Syslog-ng-使用Docker Compose方式搭建


下一篇:3月23日,每日信息差