速通ACM省铜第十一天 赋源码(Gellyfish and Flaming Peony)
引言:
今天周日,那依旧来点难度吧,昨天我们已经提升了刷题的量了,那么今天我们来提升一下题的质量,所以,今天我们只刷一题,刷一道CF评级为1500的一道题,这道题我感觉比之前打的那道1500的题难很多,还是比较抽象的,我一开始想了挺久,然后没有什么思路,后面去看题解,题解用的是dp来做的,但我看不懂(我太菜了,X_X),最终我是用bfs过的这题,因为这题的数据量并不大,就试了一手,然后就AC了,嘿嘿
今天的讲解我会把我的AC码和题解的AC码都放出来,思路应该是有区别的,因为我看不懂题解的AC码,那么接下来,就进入今天的题目讲解啦————————>
Gellyfish and Flaming Peony
按照惯例,我们先来看题目
题意分析
题目链接如下Problem - 2115A - Codeforces
不想跳转的可看下图
这题的题目意思其实很简单,就是给你一个长度为n的数组,让你进行最少的操作次数,将数组内的元素全部变成同一个元素
题目中的操作是怎么操作呢,就是任选2个下标,找出这俩个下标的元素的最大公约数,然后将这个最大公约数覆盖掉俩个里面的一个元素,这就是一步操作
要求就这么简单,那么,我们来进入题目的逻辑梳理环节
逻辑梳理
首先,要让这个数组变成同样的元素,那么这个元素的值就一定是数组中所有元素的最大公约数(把这个数设为a),这不必多说,所以我们可以分俩种情况
第一种
如果数组中有元素的大小等于a,那么只需要用这个数和别的数进行求最大公约数一次即可让一个元素变成a,所以在这种情况下,我们先统计出数组中a的个数,然后输出n-a的个数即可
第二种
如果数组中没有元素的大小等于a,那么我们就需要用方法来找出将数组中的所有元素以最小的操作次数情况下变成a的方法,这便是难点,首先直接枚举出所有情况找出最小肯定是不可能的,我是用的bfs来进行操作,不枚举出所有情况,而是枚举出在操作不同次数的情况下可以得到的所有值的情况,然后如果得到的值里有a,那么就结束循环,输出已经统计的操作次数+n-1即可
那么,该题的逻辑梳理就梳理完啦,主要是第二种情况的解决方式比较难想,那么接下来,就进入代码的实现环节
代码实现
首先,俩种情况里第一种很简单,应该是不用讲的,等下直接看代码即可,第二种情况如何实现我要浅浅讲一下
首先先创建一个vector数组a,先将开始的数组元素放入其中,然后排序完后将重复的元素删去,只需要留一个即可,因为重复的元素并不会操作的次数,还会让后面运行时需要花费的时间变多,这就是a的预处理
接下来就是使用一个真循环,直到满足条件为止才退出
在循环内再创建一个vector数组b,这个数组是为了存进行下一次操作后,a数组可能有的元素,然后再将a替换成b,接着操作
如何得到b就是将a与一开始的数组的每个元素找最大公约数,然后得到值放入b,全部循环完后再排序去重即可得到,接下来就展现我的AC码啦
我的AC码实现
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <iostream>
#include <string.h>
#include <algorithm>
#include <math.h>
#include <queue>
#include <vector>
using namespace std;int t;
int A[5010];int gcd(int a, int b)
{if (b == 0)return a;return gcd(b, a % b);
}void solve()
{int n, Gcd = 0;cin >> n;vector <int> a;long long ans = 0;for (int i = 1; i <= n; i++){cin >> A[i];Gcd = gcd(Gcd, A[i]);a.push_back(A[i]);}for (int i = 1; i <= n; i++){if (A[i] == Gcd)ans++;}if (ans){cout << n - ans << endl;return;}sort(a.begin(), a.end());a.erase(unique(a.begin(), a.end()), a.end());int xixi = 0;while (1){ans++;vector<int>b;for (int i = 0; i < a.size(); i++){for (int j = 1; j <= n; j++){int mgcd = gcd(a[i], A[j]);b.push_back(mgcd);if (mgcd == Gcd){xixi = 1;break;}}if (xixi)break;}if (xixi)break;sort(b.begin(), b.end());b.erase(unique(b.begin(), b.end()), b.end());a = b;}cout << ans + n - 1 << endl;
}int main()
{cin >> t;while (t--){solve();}return 0;
}
这个代码只适用于范围小的情况,要适用范围大的情况还是需要下面题解中的dpAC码,但我看不懂,感兴趣的可以看一下研究一下
题解的AC码实现
#include<bits/stdc++.h>using namespace std;const int N = 5000 + 5;inline void checkmax(int &x, int y){if(y > x) x = y;
}inline void checkmin(int &x, int y){if(y < x) x = y;
}int n = 0, m = 0, k = 0, a[N] = {}, f[N] = {};
int g[N][N] = {}, ans = 0;inline void solve(){scanf("%d", &n); m = k = 0;for(int i = 1 ; i <= n ; i ++){scanf("%d", &a[i]);k = g[k][a[i]];}memset(f, 0x3f, sizeof(f));for(int i = 1 ; i <= n ; i ++) a[i] /= k, checkmax(m, a[i]), f[a[i]] = 0;for(int x = m ; x >= 1 ; x --) for(int i = 1 ; i <= n ; i ++){int y = a[i];checkmin(f[g[x][y]], f[x] + 1);}ans = max(f[1] - 1, 0);for(int i = 1 ; i <= n ; i ++) if(a[i] > 1) ans ++;printf("%d\n", ans);
}int T = 0;int main(){for(int x = 0 ; x < N ; x ++) g[x][0] = g[0][x] = g[x][x] = x;for(int x = 1 ; x < N ; x ++) for(int y = 1 ; y < x ; y ++) g[x][y] = g[y][x] = g[y][x % y];scanf("%d", &T);while(T --) solve();return 0;
}
那么,这题就讲解完毕啦
结语:
今日算法讲解到此结束啦,希望对你们有所帮助,谢谢观看,如果觉得不错可以分享给朋友哟。当然也可以关注一下,有什么看不懂的可以评论问哦