【算法题解】2022河南萌新联赛第(三)场:河南大学

简介: 【算法题解】2022河南萌新联赛第(三)场:河南大学
比赛链接: https://ac.nowcoder.com/acm/contest/37782#question

墨染空大佬也来了,直接AK(orz)
在这里插入图片描述

A 玉米大炮

:tomato: 题目大意

链接: https://ac.nowcoder.com/acm/contest/37782/A
来源:牛客网

image-20220725205718478

:taco: 思路:二分答案

时间越长,击溃博士的概率越大。所以时间具有单调性,如果 x是击溃博士的最小时间,那么 x+1肯定也能击溃博士, x-1肯定无法击溃博士,所以大于 x的都能击溃,小于 x的都不能击溃,所以也具有 两段性,所以可以使用二分来做。这里我们需要注意一个细节,就是二分的左右边界,左边界最小是 0,因为第一发不需要时间。右边界是 1e18,假设只有一个大炮每次只能攻击 1生命值,装填时间是 1e9,而博士生命值是 1e9,所以最多需要 1e18时间。

:potato: 代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10, inf = 0x3f3f3f3f, mod = 998244353;
ll n, m;
ll a[N], b[N];
bool check(ll x)
{
    ll ans = 0;
    for (int i = 1; i <= n; i++)
    {
        if(a[i] * (x / b[i]) + a[i]>=m)
        {
            return true;
        }
        ans += a[i] * (x / b[i]) + a[i];
        if (ans >= m)
        return true;
    }
    return false;
}
void solve()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i] >> b[i];
    }
    ll l = 0, r = 1e18;
    while (l < r)
    {
        ll mid = (l + r) >> 1;
        if (check(mid))
            r = mid;
        else
            l = mid + 1;
    }
    cout << r;
    return;
}
signed main()
{
#ifdef Xin
    freopen("in.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif
    int T = 1;
    while (T--)
        solve();
    return 0;
}

B 逆序对计数

:tomato: 题目大意

链接: https://ac.nowcoder.com/acm/contest/37782/B
来源:牛客网

image-20220725205928561

:taco: 思路:二分答案

一个区间改变了顺序,那么不影响区间外的逆序对个数,只影响区间内的个数,一个区间如果反转了,那么此区间的逆序对个数就变成了区间总对数减去原来区间内的总逆序对个数。每次询问相互独立,所以我们只需要记录每个区间的逆序对个数就可以解决问题了。考虑从 [l,r] ==> [l, r + 1]的逆序对个数增量,可以记录 [l, r]区间中必 a[r + 1]大的元素个数。统计完直接记录即可。当然也可以用树状数组求逆序对或者归并排序。

:potato: 代码

动态规划版

#include <bits/stdc++.h>
using namespace std;
const int N = 6e3 + 10, inf = 0x3f3f3f3f, mod = 998244353;
int f[N][N]; // l到r地区间内的逆序对数量
int a[N];
int n, q;
void solve()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    for (int i = n; i >= 1; i--)
    {
        int cnt = 0;
        for (int j = i + 1; j <= n; j++)
        {
            f[i][j] = f[i + 1][j]; // [l,r]内的区间逆序对个数就等于[l + 1, r] 区间内的个数在加上有了l之后的个数
            if (a[i] > a[j])
                cnt++;//统计有了l后[l,r]区间内的逆序对增量
            f[i][j] += cnt;
        }
    }
    cin >> q;
    while (q--)
    {
        int l, r;
        cin >> l >> r;
        int len = r - l + 1;
        int ans = f[1][n] - f[l][r] + (len - 1) * len / 2 - f[l][r];
        cout << ans << endl;
    }
}
signed main()
{
#ifdef Xin
    freopen("in.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif
    int T = 1;
    while (T--)
        solve();
    return 0;
}

C.区间操作

:popcorn: 题目

链接: https://ac.nowcoder.com/acm/contest/37782/C
来源:牛客网

image-20220725211309005

:peach: 思路:线性筛 + 线段树

我们先来推导一下:

image-20220725212502656

这样我们就可以知道要求什么了。我们需要记录每个b[i]的所有质因数的指数的和。

每次都是求区间的和和修改区间,所以我们可以使用线段树,由于多次修改区间,所以还需要加懒标记。

这里还有一个问题,如果我们暴力求每个数漂亮值,时间复杂度O(根号x)那么一定会超时。x最大是4e6,所以我们只需要求出2000以内的所有质数就行(大约300个),那么那么求每一个数的指数个数的话就只需要处理300次即可。求质数我们可以使用线性筛。

:pear: 代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10, inf = 0x3f3f3f3f, mod = 998244353;
int n, m;
int w[N];
bool st[N];
int primes[N];
int cnt1;
struct Node
{
    int l, r;
    ll sum, add;
} tr[N * 4];
void init()
{
    for (int i = 2; i <= 2e3; i++)
    {
        if (!st[i])
        {
            primes[cnt1++] = i;
        }
        for (int j = 0; primes[j] <= 2e3 / i; j++)
        {
            st[i * primes[j]] = true;
            if (i % primes[j] == 0)
            {
                break;
            }
        }
    }
}
// 求一个数的漂亮值
int get(int d)
{
    int cnt = 0;
    for (int i = 0; i < cnt1; i++)
    {
        if (d % primes[i] == 0)
        {
            while (d % primes[i] == 0)
            {
                cnt++;
                d /= primes[i];
            }
        }
    }
    if(d > 1)
        cnt++;
    return cnt;
}
void pushup(int u)
{
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void pushdown(int u)
{
    if (tr[u].add)
    {
        int d = tr[u].add;
        tr[u << 1].add += d;
        tr[u << 1].sum += d * (tr[u << 1].r - tr[u << 1].l + 1);
        tr[u << 1 | 1].add += d;
        tr[u << 1 | 1].sum += (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1) * d;
        tr[u].add = 0;
    }
}
void build(int u, int l, int r)
{
    if (l == r)
    {
        tr[u] = {l, l, get(w[l]), 0};
    }
    else
    {
        tr[u] = {l, r};
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}
void modify(int u, int l, int r, int d)
{
    if (tr[u].l >= l && tr[u].r <= r)
    {
        tr[u].sum += d * (tr[u].r - tr[u].l + 1);
        tr[u].add += d;
    }
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid)
            modify(u << 1, l, r, d);
        if (r > mid)
            modify(u << 1 | 1, l, r, d);
        pushup(u);
    }
}
ll query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r)
        return tr[u].sum;
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        ll sum = 0;
        if (l <= mid)
            sum += query(u << 1, l, r);
        if (r > mid)
            sum += query(u << 1 | 1, l, r);
        return sum;
    }
}
void solve()
{
    init();
    cin >> n;
    for (int i = 1; i <= n; i++)
        scanf("%d",&w[i]);
    build(1, 1, n);
    int q;
    cin >> q;
    while (q--)
    {
        ll k, l, r, s;
        cin>>k>>l>>r;
        if (k == 1)
        {
            cout << query(1, l, r) << endl;
        }
        else
        {
            cin>>s;
            s= get(s);
            modify(1, l, r, s);
        }
    }
}
signed main()
{
#ifdef Xin
    freopen("in.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif
    int T = 1;
    while (T--)
        solve();
    return 0;
}

H 树上问题

:ear_of_rice: 题目

链接: https://ac.nowcoder.com/acm/contest/37782/H
来源:牛客网

image-20220726175012207

:ice_cream: 思路:树形DP

一个点的代价最大代价就等于从这个点到其他点的最长路径。我们需要求三个相连的点的最长路径和,那么首先我们就需要先求出每个点的最大代价。

那如何求一个点到其他点的最长路径呢?

一个点到其他点的最长路径无非是从它的几个子节点和它的父节点中寻找最长路径在加上自己的值。也就是向上走或者向下走的问题。

向下走是很容易的,我们只需要使用dfs寻找每一个点向下走的最长路径,然后在用子节点更新父节点。这里利用了回溯的特点。寻找向下走的最长距离还需要记录次长距离和分别是从哪个点下去的。具体为什么要求次长距离我们等一下在求向上走的时候需要用到。

image-20220726181425526

假设我们当前正在求2号点向下走的情况,可以遍历它的所有子节点56,在记录它的所有子节点的路径,保留最长路径和次长路径即可,通过一次dfs遍历我们就可以球的所有点向下走的最长路径。

我们再来看看向上走的情况,假设我们当前正在求5号点向上走的最长路径,那么它需要在2号点的其他子节点的最长路径以及2号点向上走的最长路径中找。如果我们一个一个找的话就回超时。这时我们在向下走时得到的最长距离和次长距离就排上了用场。

  • 5号点位于最长路径上,那么它向上走的距离就取2号点的子节点次长距离和2号点向上最长距离的最大值即可。这样我们的复杂度是O(1)的。

image-20220726183319977

  • 假设5号节点不在2号点的最长路径上,那么一定2好点的最长距离一定在它的其他子节点上。那5号点向上走的最长路径就等于2号点向上走的最长路径和2号点向下走的最长路径取最大值即可。

image-20220726183720499

求得每个点向上走和向下走的最长路径之后,它俩取最大值就是这个点到其他点的最长路径。

接下来就是求三个相连的点的最长路径的和的最大值了。如果求呢?

有两种情况:

  • 一个点和它的两个子节点组成的三个点的路径和最大
  • 一个点和它的儿子以及它的儿子的儿子组成的三个点的路径和最大

我们只需要遍历每个点,记录它的儿子中最大和次大的两个儿子,以及它儿子和它孙子的路径和,最后取最大值即可,如下图所示

image-20220726184410847

:jack_o_lantern: 代码

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 10, M = N * 2;
int h[N], e[M], ne[M], idx;
int n, a[N];
int w[N];  //当前这个点到叶子节点的最长路径
int d1[N]; //当前这个点向下走的最长路径
int d2[N]; //当前这个点向下走的次长路径
int up[N]; //当前这个点向上早的最长路径
int p1[N]; //当前这个点的最长路径是从哪一个子节点下去的
int p2[N]; //当前这个点的次长路径是从哪一个子节点下去的
int ans;
void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
int dfs_d(int u, int fa)
{
    d1[u] = d2[u] = a[u];
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (j == fa)
            continue;
        int d = dfs_d(j, u) + a[u];
        if (d >= d1[u]) //这里一定要大于等于
        {
            p2[u] = p1[u];
            d2[u] = d1[u];
            p1[u] = j;
            d1[u] = d;
        }
        else if (d > d2[u])
        {
            p2[u] = j;
            d2[u] = d;
        }
    }
    return d1[u];
}
void dfs_u(int u, int fa)
{
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (j == fa)
            continue;
        if (p1[u] == j)
            up[j] = max(up[u], d2[u]) + a[j];
        else
            up[j] = max(d1[u], up[u]) + a[j];
        dfs_u(j, u);
    }
}
int dfs(int u, int fa)
{
    int max1, max2, max3;
    max1 = max2 = max3 = 0;
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (j == fa)
            continue;
        if (w[j] >= max1)
        {
            max2 = max1;
            max1 = w[j];
        }
        else if (w[j] > max2)
        {
            max2 = w[j];
        }
        max3 = max(max3, dfs(j, u));
        ans = max(ans, w[u] + w[j] + max3);
    }
    ans = max(ans, (w[u] + max1 + max2));
    return max1;
}
signed main()
{
    memset(h, -1, sizeof h);
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    for (int i = 1; i < n; i++)
    {
        int a, b;
        cin >> a >> b;
        add(a, b);
        add(b, a);
    }
    // 求d1、d2、p1、p2数组
    dfs_d(1, -1);
    // 这里一定要初始化1的up数组
    up[1] = a[1];
    // 求 up数组
    dfs_u(1, -1);
    // 求w数组
    for (int i = 1; i <= n; i++)
        w[i] = max(d1[i], up[i]);
    // 计算总价值
    dfs(1, -1);
    cout << ans;
    return 0;
}

I 旅行

:star: 题目

链接: https://ac.nowcoder.com/acm/contest/37782/I
来源:牛客网

image-20220726102426667

:night_with_stars: 思路:分层图求最短路

每一个城市都有两种状态,一种是要做核酸,一种是不做。如果上一个城市做了核酸,那么当前这个城市就不用做,如果上个城市没做,则我当前一定要做。所以每个城市都有可能做核酸或者没做核酸,而后直接跑最短路即可

:yum: 代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long ll;
#define int long long
#define x first
#define y second
typedef priority_queue<int, vector<int>, less<int>> Q;
typedef pair<ll, pair<int, int>> PII;
const int N = 1e6 + 10, M = 1e6 + 10;
ll h[N], e[M], w[M], ne[M], idx;
ll dist[N][2];
bool st[N][2];
int n, m, x;
int ans;
void add(int a, int b, int c) // 添加一条边a->b,边权为c
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1][0] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, {1, 0}});
    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();
        int ver = t.y.x;
        int state = t.y.y;
        ll d = t.x;
        if (st[ver][state])
            continue;
        st[ver][state] = true;
        for (int i = h[ver]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (state == 0)
            {
                if (dist[j][1] > dist[ver][0] + w[i] + x)
                {
                    dist[j][1] = dist[ver][0] + w[i] + x;
                    heap.push({dist[j][1], {j, 1}});
                }
            }
            else
            {
                if (dist[j][0] > dist[ver][1] + w[i])
                {
                    dist[j][0] = dist[ver][1] + w[i];
                    heap.push({dist[j][0], {j, 0}});
                }
            }
        }
    }
}

signed main()
{
    memset(h, -1, sizeof h);
    cin >> n >> m >> x;
    while (m--)
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
        add(b, a, c);
    }
    dijkstra();
    cout << min(dist[n][0],dist[n][1]);
    return 0;
}

J 神奇数字

:star: 题目

链接: https://ac.nowcoder.com/acm/contest/37782/J
来源:牛客网

image-20220726104121394

:night_with_stars: 思路:试除法求约数

image-20220726104810933

要想让两个数同余,那么它俩的差一定是x的倍数,三个数同余,那就是两两之间的差的倍数。一个数是几个数的约数,那么它一定是这几个数最大公约数的约数。

所以我们只需要求出最大公约数,然后在求最大公约数的约数即可。

:yum: 代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10, inf = 0x3f3f3f3f, mod = 998244353;
void solve()
{
    int a[3] = {0};
    cin >> a[0] >> a[1] >> a[2];
    sort(a, a + 3);
    if (a[0] == a[2])
    {
        puts("-1");
        return;
    }
    int x = __gcd((a[1] - a[0]), (a[2] - a[1]));
    vector<int> v;
    for (int i = 1; i <= x / i; i++)
    {
        if (x % i == 0)
        {
            v.push_back(i);
            if (i != x / i)
            {
                v.push_back(x / i);
            }
        }
    }
    sort(v.begin(), v.end());
    for (int i = 0; i < v.size(); i++)
        cout << v[i]<<" ";
    cout << endl;
}
signed main()
{
#ifdef Xin
    freopen("in.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif
    int T = 1;
    cin >> T;
    while (T--)
        solve();
    return 0;
}

:love_letter: 剩下的题慢慢补了,有任何疑问欢迎留言评论

相关文章
|
2天前
|
算法 数据安全/隐私保护
室内障碍物射线追踪算法matlab模拟仿真
### 简介 本项目展示了室内障碍物射线追踪算法在无线通信中的应用。通过Matlab 2022a实现,包含完整程序运行效果(无水印),支持增加发射点和室内墙壁设置。核心代码配有详细中文注释及操作视频。该算法基于几何光学原理,模拟信号在复杂室内环境中的传播路径与强度,涵盖场景建模、射线发射、传播及接收点场强计算等步骤,为无线网络规划提供重要依据。
|
15天前
|
机器学习/深度学习 算法
基于改进遗传优化的BP神经网络金融序列预测算法matlab仿真
本项目基于改进遗传优化的BP神经网络进行金融序列预测,使用MATLAB2022A实现。通过对比BP神经网络、遗传优化BP神经网络及改进遗传优化BP神经网络,展示了三者的误差和预测曲线差异。核心程序结合遗传算法(GA)与BP神经网络,利用GA优化BP网络的初始权重和阈值,提高预测精度。GA通过选择、交叉、变异操作迭代优化,防止局部收敛,增强模型对金融市场复杂性和不确定性的适应能力。
149 80
|
3天前
|
机器学习/深度学习 数据采集 算法
基于GA遗传优化的CNN-GRU-SAM网络时间序列回归预测算法matlab仿真
本项目基于MATLAB2022a实现时间序列预测,采用CNN-GRU-SAM网络结构。卷积层提取局部特征,GRU层处理长期依赖,自注意力机制捕捉全局特征。完整代码含中文注释和操作视频,运行效果无水印展示。算法通过数据归一化、种群初始化、适应度计算、个体更新等步骤优化网络参数,最终输出预测结果。适用于金融市场、气象预报等领域。
基于GA遗传优化的CNN-GRU-SAM网络时间序列回归预测算法matlab仿真
|
3天前
|
算法
基于龙格库塔算法的锅炉单相受热管建模与matlab数值仿真
本设计基于龙格库塔算法对锅炉单相受热管进行建模与MATLAB数值仿真,简化为喷水减温器和末级过热器组合,考虑均匀传热及静态烟气处理。使用MATLAB2022A版本运行,展示自编与内置四阶龙格库塔法的精度对比及误差分析。模型涉及热传递和流体动力学原理,适用于优化锅炉效率。
|
1天前
|
移动开发 算法 计算机视觉
基于分块贝叶斯非局部均值优化(OBNLM)的图像去噪算法matlab仿真
本项目基于分块贝叶斯非局部均值优化(OBNLM)算法实现图像去噪,使用MATLAB2022A进行仿真。通过调整块大小和窗口大小等参数,研究其对去噪效果的影响。OBNLM结合了经典NLM算法与贝叶斯统计理论,利用块匹配和概率模型优化相似块的加权融合,提高去噪效率和保真度。实验展示了不同参数设置下的去噪结果,验证了算法的有效性。
|
9天前
|
机器学习/深度学习 算法
基于遗传优化的双BP神经网络金融序列预测算法matlab仿真
本项目基于遗传优化的双BP神经网络实现金融序列预测,使用MATLAB2022A进行仿真。算法通过两个初始学习率不同的BP神经网络(e1, e2)协同工作,结合遗传算法优化,提高预测精度。实验展示了三个算法的误差对比结果,验证了该方法的有效性。
|
11天前
|
机器学习/深度学习 数据采集 算法
基于PSO粒子群优化的CNN-GRU-SAM网络时间序列回归预测算法matlab仿真
本项目展示了基于PSO优化的CNN-GRU-SAM网络在时间序列预测中的应用。算法通过卷积层、GRU层、自注意力机制层提取特征,结合粒子群优化提升预测准确性。完整程序运行效果无水印,提供Matlab2022a版本代码,含详细中文注释和操作视频。适用于金融市场、气象预报等领域,有效处理非线性数据,提高预测稳定性和效率。
|
7天前
|
算法
基于梯度流的扩散映射卡尔曼滤波算法的信号预处理matlab仿真
本项目基于梯度流的扩散映射卡尔曼滤波算法(GFDMKF),用于信号预处理的MATLAB仿真。通过设置不同噪声大小,测试滤波效果。核心代码实现数据加载、含噪信号生成、扩散映射构建及DMK滤波器应用,并展示含噪与无噪信号及滤波结果的对比图。GFDMKF结合非线性流形学习与经典卡尔曼滤波,提高对非线性高维信号的滤波和跟踪性能。 **主要步骤:** 1. 加载数据并生成含噪测量值。 2. 使用扩散映射捕捉低维流形结构。 3. 应用DMK滤波器进行状态估计。 4. 绘制不同SNR下的轨迹示例。
|
12天前
|
机器学习/深度学习 算法 索引
单目标问题的烟花优化算法求解matlab仿真,对比PSO和GA
本项目使用FW烟花优化算法求解单目标问题,并在MATLAB2022A中实现仿真,对比PSO和GA的性能。核心代码展示了适应度计算、火花生成及位置约束等关键步骤。最终通过收敛曲线对比三种算法的优化效果。烟花优化算法模拟烟花爆炸过程,探索搜索空间,寻找全局最优解,适用于复杂非线性问题。PSO和GA则分别适合快速收敛和大解空间的问题。参数调整和算法特性分析显示了各自的优势与局限。
|
6天前
|
算法 5G
基于MSWA相继加权平均的交通流量分配算法matlab仿真
本项目基于MSWA(Modified Successive Weighted Averaging)相继加权平均算法,对包含6个节点、11个路段和9个OD对的交通网络进行流量分配仿真。通过MATLAB2022A实现,核心代码展示了迭代过程及路径收敛曲线。MSWA算法在经典的SUE模型基础上改进,引入动态权重策略,提高分配结果的稳定性和收敛效率。该项目旨在预测和分析城市路网中的交通流量分布,达到用户均衡状态,确保没有出行者能通过改变路径减少个人旅行成本。仿真结果显示了27条无折返有效路径的流量分配情况。

热门文章

最新文章

下一篇
开通oss服务