牛客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) (1≤n≤2000),互相转账的对数 m m m ( m ≤ 1 0 5 ) (m ≤ 10^{5}) (m≤105).
分析:
图论最短路算法,我们将手续费看成经过这一次转账需要花费的费用,则我们要使得从 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}
x∗100%−x∗z%=100 ⟹ x=100∗100−z100
在跑最短路算法时直接用上面的公式即可。观察数据范围,这里用 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(1≤n≤100),ai(1≤ai≤100)
分析:
化简一下,题目需要解决两个问题:
- 什么情况下是有无穷个凑不出来的数字的,即输出 I N F INF INF的情况。
- 如何知道哪些数字是凑不出来的。
问题一:
关于第一个问题,我们设 n n n个数的最大公因数为 d d d,那么这意味着所有的 a i ( 1 ≤ i ≤ n ) a_{i}(1≤i≤n) ai(1≤i≤n)都能被 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}) b1⋅a1+b2⋅a2+...+bn⋅an(0≤bi)由于每个 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 b1⋅a1+b2⋅a2+...+bn⋅an=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(1≤ai≤n) 保证
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;
}