刷题记录01

文章解析了三道编程题目,涉及图论中的最小生成树问题、最短路算法应用以及数学优化问题,如计算权值乘积末尾0的数量、最小费用转账和数字组合问题。解题思路包括使用最小生成树策略、Dijkstra算法和背包问题变种等。
摘要由CSDN通过智能技术生成

牛客Round22 D题

题目大意:

链接:https://ac.nowcoder.com/acm/contest/70996/D

你有一个 n n n个节点、 m m m条边的无向连通图,每个节点的权值已知。
当你删掉一条边时,可以获得连接该边的两个节点“权值乘积末尾 0 0 0数量”的价值。例如,一条边连接的两个点权值是50和60,那么你删掉这条边获得的价值为3。
现在,在保证这张图连通的情况下,你最多可以通过删边获得多少价值?

分析:

肯定一眼图论问题嘛,我们将一条边的权值记作两节点“权值乘积末尾0的数量”。因为我们在删边的同时要保证这张图是连通,留下来的边是我们所拿不到的价值,所以我们要使得留下来的边越少,价值越小,所以一眼最小生成树。

特别的因为可能点的权值很大,导致两点相乘超过了long long的数据范围,这里我们对每个点提取出 2 2 2 5 5 5的个数,即可算出末尾 0 0 0的个数。

AC代码:

#include <bits/stdc++.h>
using i64 = long long ;

const int N = 1e5 + 10;

int n, m, fa[N];
i64 rec[N][2];

struct node {
    int x, y; i64 z;
    bool operator < (node &t) const {
        return z < t.z;
    }
} edge[N];

int find (int x) {  return x == fa[x] ? x : fa[x] = find(fa[x]);    }

void solve()
{
    std::cin >> n >> m;
    for (int i = 1; i <= n; i ++) {
        i64 now;    std::cin >> now;
        while (now % 2 == 0) {    now /= 2;    rec[i][0] ++;    }
        while (now % 5 == 0) {    now /= 5;    rec[i][1] ++;    }
    }
    i64 ans = 0, res = 0;
    for (int i = 1; i <= m; i ++) {
        int u, v;    std::cin >> u >> v;
        edge[i] = {u, v, 0};
        ans = ans + (edge[i].z = std::min(rec[u][0] + rec[v][0], rec[u][1] + rec[v][1]));
    } std::sort(edge + 1, edge + m + 1);
    
    for (int i = 1; i <= n; i ++)    fa[i] = i;
    for (int i = 1; i <= m; i ++) {
        int x = find(edge[i].x), y = find(edge[i].y);
        if (x == y)    continue ;
        fa[x] = y;
        res += edge[i].z;
    }
    
//    std::cout << "ans = " << ans << '\n' << "res = " << res << '\n';
    std::cout << ans - res << '\n';
    return ;
}

int main()
{
    std::ios::sync_with_stdio(false);std::cin.tie(nullptr);std::cout.tie(nullptr);
    int _ = 1;    // std::cin >> (_);
    while (_ --) {
        solve();
    }
    return 0;
}

AcWing1126 最小花费

题目大意:

n n n个人中,某些人之间可以互相转账,这些人之间转账的手续费各不相同。给定这些人之间转账时需要从转账金额中扣除 z % z\% z% ( z < 100 ) (z < 100) (z<100)的手续费。

请问从 s s s 至少需要多少钱使得经过转账后到 t t t 收到100元。

数据范围:人数 n n n ( 1 ≤ n ≤ 2000 ) (1 ≤ n ≤ 2000) (1n2000),互相转账的对数 m m m ( m ≤ 1 0 5 ) (m ≤ 10^{5}) (m105).

分析:

图论最短路算法,我们将手续费看成经过这一次转账需要花费的费用,则我们要使得从 s s s t t t 的花费最小。

由于我们是要求初始时的最小费用,则我们可以反向求,即从 t t t s s s满足如下公式:
x ∗ 100 % − x ∗ z % = 100   ⟹   x = 100 ∗ 100 100 − z x * 100\% - x * z\% = 100 \ \Longrightarrow \ x = 100 * {100 \over 100 - z} x100%xz%=100  x=100100z100

在跑最短路算法时直接用上面的公式即可。观察数据范围,这里用 d i j k s t r a dijkstra dijkstra朴素版完全可以。

ps. 注意初始化,我就是因为一开始没有初始化导致 w a wa wa了两发.

AC代码:

#include <bits/stdc++.h>
#define gps(i) std::setprecision(i)

const int N = 2005;
const double inf = 2e18;

int n, m;
int s, t;
int g[N][N];

double dijkstra()
{
    std::vector<bool> st(n + 5, false);
    std::vector<double> dist(n + 5, inf);
    
    dist[t] = 100;
    for (int i = 1; i <= n; i ++) {
        int now = -1;
        for (int j = 1; j <= n; j ++) {
            if (!st[j] && (now == -1 || dist[now] > dist[j])) {
                now = j;
            }
        }
        st[now] = true;
        for (int j = 1; j <= n; j ++) {
            dist[j] = std::min(dist[j], (dist[now] * 100) / (100 - g[now][j]));
        }
    }
    
    return dist[s];
}

void solve()
{
    std::cin >> n >> m;
    
    for (int i = 1; i <= n; i ++) {
        for (int j = i; j <= n; j ++) {
            g[i][j] = g[j][i] = 99;
        }
    }
    
    for (int i = 1; i <= m; i ++) {
        int u, v, w;    std::cin >> u >> v >> w;
        g[u][v] = g[v][u] = std::min(g[u][v], w);
    }
    
    std::cin >> s >> t;
    std::cout << std::fixed << gps(8) << dijkstra() << '\n';
    return ;
}

int main()
{
    std::ios::sync_with_stdio(false);std::cin.tie(nullptr);std::cout.tie(nullptr);
    int _ = 1;  // std::cin >> (_);
    while (_ --) {
        solve();
    }
    return 0;
}

蓝桥网备赛SaaS专业版题库.43689包子凑数

题目大意:

你有一个大小为 n n n的数组,每个数都能使用无限次,问你不能用这些数字组成的数字个数是多少,如果有无穷个,则输出 I N F INF INF

范围: n ( 1 ≤ n ≤ 100 ) , a i ( 1 ≤ a i ≤ 100 ) n (1 ≤ n ≤ 100), a_{i} (1 ≤ a_{i} ≤ 100) n(1n100),ai(1ai100)

分析:

化简一下,题目需要解决两个问题:

  1. 什么情况下是有无穷个凑不出来的数字的,即输出 I N F INF INF的情况。
  2. 如何知道哪些数字是凑不出来的。

问题一:

关于第一个问题,我们设 n n n个数的最大公因数为 d d d,那么这意味着所有的 a i ( 1 ≤ i ≤ n ) a_{i}(1≤i≤n) ai(1in)都能被 d d d整除。现在考虑表达式 b 1 ⋅ a 1 + b 2 ⋅ a 2 + . . . + b n ⋅ a n ( 0 ≤ b i ) b_{1} \cdot a_{1} + b_{2} \cdot a_{2} + ... + b_{n} \cdot a_{n}(0 ≤ b_{i}) b1a1+b2a2+...+bnan(0bi)由于每个 a i a_{i} ai都能被 d d d整除,所以整个表达式也能被 d d d整除。换句话说,对于任何整数 b 1 , b 2 , … , b n b_{1}, b_{2}, …, b_{n} b1,b2,,bn,表达式的值都是 d d d的倍数。

设: b 1 ⋅ a 1 + b 2 ⋅ a 2 + . . . + b n ⋅ a n ≠ x b_{1} \cdot a_{1} + b_{2} \cdot a_{2} + ... + b_{n} \cdot a_{n} ≠ x b1a1+b2a2+...+bnan=x

d > 1 d > 1 d>1时对于任何 x   m o d   d ≠ 0 x \ mod \ d ≠ 0 x mod d=0的情况写,我们总能构造一个数字 x x x使得上式成立,并对于 x x x的倍数 k x kx kx也仍然成立。

即得到结论,当 n n n个数的最大公约数不是 1 1 1的时候,我们有无穷个不能构造的数字,则输出 I N F INF INF

问题二:

背包问题变种秒了,这里我们判断答案是有穷时,直接开一个最大值上限开到 1 e 6 1e6 1e6即可,这样时间复杂度为 O ( 1 e 6 n ) O(1e6n) O(1e6n)不会超时。

AC代码

#include <bits/stdc++.h>
using i64 = long long ;

const int N = 110, INF = 1e6 + 10;
int n;
int a[N];

inline int gcd (int x, int y) { return y == 0 ? x : gcd(y, x % y);  }

void solve()
{
    std::cin >> n;

    int isFlag = 0;
    std::vector<i64> dp(INF, 0);
    for (int i = 1; i <= n; i ++) {
        std::cin >> a[i];
        dp[a[i]] = 1;
        if (i == 1) isFlag = a[i];
        else isFlag = gcd(isFlag, a[i]);
    }

    if (isFlag != 1)    std::cout << "INF" << '\n';
    else {
        int ans = 0;
        for (int i = 1; i < INF; i ++) {
            for (int j = 1; j <= n; j ++) {
                if (i < a[j])   continue ;
                else    dp[i] |= dp[i - a[j]];
            }
            if (dp[i] == 0) ans ++;
        }
        std::cout << ans << '\n';
    }
    return ;
}

int main()
{
    std::ios::sync_with_stdio(false);std::cout.tie(nullptr);std::cout.tie(nullptr);
    int _ = 1;  // std::cin >> (_);
    while (_ --) {
        solve();
    }
    return 0;
}

蓝桥网备赛SaaS专业版题库.43702交换瓶子

题目大意:

n n n个数字,每次我们可以拿起两个并交换它们,问至少多少次可以使得 n n n个数变为升序的。
n ( 0 < n < 1 0 4 ) , a i ( 1 ≤ a i ≤ n ) n (0 < n < 10^{4}), a_{i}(1 ≤ a_{i}≤ n) n(0<n<104),ai(1ain) 保证 a i a_{i} ai两两不相同

分析:

方法一:暴力

数据范围不大直接暴力

AC代码:

#include <bits/stdc++.h>

const int N = 1e4 + 10;

int n;
int a[N];

void solve()
{
    std::cin >> n;
    for (int i = 1; i <= n; i ++) {
        std::cin >> a[i];
    }

    int ans = 0;
    for (int i = 1; i <= n; i ++) {
        if (a[i] != i) {
            for (int j = i + 1; j <= n; j ++) {
                if (a[j] == i) {
                    std::swap(a[i], a[j]);
                }
            }
            ans ++;
        }
    }

    std::cout << ans << '\n';
    return ;
}

int main()
{
    std::ios::sync_with_stdio(false);std::cin.tie(nullptr);std::cout.tie(nullptr);
    int _ = 1;  //  std::cin >> (_);
    while (_ --) {
        solve();
    }
    return 0;
}

方法二:图论

这题因为数据比较小所以可以直接暴力,但是在数据范围比较大的时候,我们可以用图论,将 i i i a [ i ] a[i] a[i]相连接,再将所有连通图中的点数 − 1 -1 1,即是将这个连通图还原为正常顺序的最少次数。最后加起来即可。

AC代码:

#include <bits/stdc++.h>

const int N = 1e4 + 10;

int n;
int a[N];
bool st[N];
int h[N], ne[N], e[N], idx;

void add(int u, int v) {
    e[idx] = v; ne[idx] = h[u]; h[u] = idx ++;
}

int dfs(int u) {
    int res = 1;
    st[u] = true;
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (st[j] == false) {
            res += dfs(j);
        }
    }
    return res;
}

void solve()
{
    std::cin >> n;
    std::memset(h, -1, sizeof h);

    for (int i = 1; i <= n; i ++) {
        std::cin >> a[i];
        add (a[i], i);
        add (i, a[i]);
    }
    
    int ans = 0;
    for (int i = 1; i <= n; i ++) {
        if (st[a[i]] == false) {
            ans += dfs(a[i]) - 1;
        }
    }
    std::cout << ans << '\n';
    return ;
}

int main()
{
    std::ios::sync_with_stdio(false);std::cin.tie(nullptr);std::cout.tie(nullptr);
    int _ = 1;  //  std::cin >> (_);
    while (_ --) {
        solve();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值