蓝桥杯
蓝桥杯题型分类2
素数
孪生素数
#include <bits/stdc++.h>
using namespace std;
// 定义最大范围,m <= 100,因此数组大小只需要开到 110 即可
const int N = 1e2 + 10;
// primes 用于存储素数,cnt 记录素数的数量
int primes[N], cnt;
// st[i] 如果为 true 表示 i 已经被筛选掉(非素数)
bool st[N];
// 获取 [2, n] 范围内的所有素数,采用线性筛法
void get_primes(int n) {
for (int i = 2; i <= n; i++) {
// 如果 st[i] 为 false,说明 i 还是素数
if (!st[i]) {
// 将该素数存入 primes 数组,并计数
primes[cnt++] = i;
}
// 遍历用来进行筛选的素数列表
for (int j = 0; primes[j] <= n / i; j++) {
// 将当前下标对应的合数标记为 true
st[primes[j] * i] = true;
// 若当前数 i 能被 primes[j] 整除,就退出,避免重复筛
if (i % primes[j] == 0) break;
}
}
}
int main() {
int m;
cin >> m; // 读取输入
// 获取 [2, m] 范围内所有素数
get_primes(m);
// 从后往前遍历 primes 数组,查找相隔为 2 的孪生素数对
for (int i = cnt - 1; i >= 1; i--) {
// 检查相邻两素数之差是否为 2
if (primes[i] - primes[i - 1] == 2) {
// 输出这对最大孪生素数
cout << primes[i - 1] << " " << primes[i];
break;
}
}
return 0;
}
素数个数
朴素筛法求素数
int primes[N], cnt; // primes[]存储所有素数
bool st[N]; // st[x]存储x是否被筛掉
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (st[i]) continue;
primes[cnt ++ ] = i;
for (int j = i + i; j <= n; j += i)
st[j] = true;
}
}
线性筛法求素数
int primes[N], cnt; // primes[]存储所有素数
bool st[N]; // st[x]存储x是否被筛掉
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) primes[cnt ++ ] = i;
for (int j = 0; primes[j] <= n / i; j ++ )
{
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
因数分解
试除法分解质因数
void divide(int x)
{
for (int i = 2; i <= x / i; i ++ )
if (x % i == 0)
{
int s = 0;
while (x % i == 0) x /= i, s ++ ;
cout << i << ' ' << s << endl;
}
if (x > 1) cout << x << ' ' << 1 << endl;
cout << endl;
}
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const ll N=1e12;
void divide(ll x)
{
for(ll i=2;i<=x/i;i++)
{
if(x%i==0)
{
int s=0;
while(x%i==0)
{
x/=i;
s++;
}
if(s==1)
{
cout<<i<<" * ";
}
else
{
cout<<i<<'^'<<s<<" * ";
}
}
}
if(x>1)cout<<x<<endl;
}
void solve()
{
ll n;
cin>>n;
divide(n);
}
int main()
{
solve();
return 0;
}
等差素数列
#include <bits/stdc++.h>
using namespace std;
// 使用ll表示long long,方便处理较大数值
using ll = long long;
// N 定义为 1e8,表示可能存储的最大范围
const ll N = 1e8;
// primes 数组用于存储素数,cnt 表示当前素数数量
int primes[N], cnt;
// st 数组标记某个数是否为合数,如果 st[x] = true,表示 x 不是素数
bool st[N];
// 筛选素数函数,埃氏筛或线性筛的思路
void get_primes(int n)
{
// 从 2 开始判断每个数
for(int i = 2; i <= n; i++)
{
// 如果 st[i] == false,说明 i 是素数
if(!st[i]) primes[cnt++] = i;
// 使用已得到的素数来标记合数
for(int j = 0; primes[j] <= n / i; j++)
{
// 将 i * primes[j] 标记为合数
st[primes[j] * i] = true;
// 如果 i 能被当前素数整除,则无需再继续标记
if(i % primes[j] == 0) break;
}
}
}
int main()
{
// 先筛选出 10000 以内的素数
get_primes(10000);
// 枚举可能的首项,从 2 开始到 10000
for(int head = 2; head <= 10000; head++)
{
// 如果 head 是素数(st[head] == false),再去尝试不同的公差 d
if(!st[head])
{
// 从公差 d=2 开始枚举到 1000
for(int d = 2; d <= 1000; d++)
{
// len 用于统计当前等差序列中连续的素数个数,这里初始设为 1
int len = 1;
// 从 head 开始,每次加上公差 d
for(int j = head; ; j += d)
{
// 如果当前数 j 仍为素数
if(!st[j])
{
len++;
// 当连续素数个数达到 10 时,说明找到一个长度为 10 的等差素数序列
if(len == 10)
{
// 输出此时的公差 d 并直接结束
cout << d << endl;
return 0;
}
}
// 遇到合数时,当前公差无法构成长度 10 的素数序列,跳出循环
else
{
break;
}
}
}
}
}
return 0;
}
梅森素数
在这段程序中,a[1] 初始化为 1,然后在整个运算过程中它并不会回到 0。
具体原因是:
• a[1] 一开始被设为 1(代表当前值的最低位)。
• 随后的循环每次都对所有位进行乘 2 运算,此时最低位始终会在 2、4、8、6 之间循环(或产生进位后继续保持非零),不会变为 0。
• 最后减去 1 时,也只是在原先非零的最低位上进行简单的 -1 操作,因此不会出现 a[1] 是 0 而再继续减 1 的问题。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
// 初始化一个大小为101的整型向量a,所有元素初始为0,用于模拟“高精度”存储数字的每一位。
// 下标1到100将存储数字的各位(下标1表示最低位,100最高位)。
vector<int> a(101, 0);
// 将最低位初始化为1,相当于保存数字"1"。
a[1] = 1;
// 这层循环共执行11213次,相当于做 2^11213 的计算
for (int i = 1; i <= 11213; ++i) {
int c = 0; // c用来记录进位
// 将每一位都乘以2,再加上上一位留下的进位c
for (int j = 1; j <= 100; ++j) {
a[j] = a[j] * 2 + c; // 乘2并加进位
c = a[j] / 10; // 计算新的进位值
a[j] %= 10; // 当前位保留一位,超过10的部分变成下一位进位
}
// 此时若c不为0,说明还产生新的进位,不过只要求后100位,不再额外处理进位
}
// 计算 2^11213 - 1,即将结果最低位减1
a[1] -= 1;
// 从最高位到最低位输出,拼接成完整的数字串
for (int i = 100; i >= 1; --i) {
cout << a[i];
}
return 0;
}
组素数
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=1e4;
int primes[N],cnt;
bool st[N];
void get_primes(int n)
{
for(int i=2;i<=n;i++)
{
if(!st[i])primes[cnt++]=i;
for(int j=0;primes[j]<=n/i;j++)
{
st[primes[j]*i]=true;
if(i%primes[j]==0)break;
}
}
}
int main()
{
get_primes(10000);
int ans=0;
if (!st[1949]) ans++;
if (!st[1499]) ans++;
if (!st[1994]) ans++;
if (!st[4199]) ans++;
if (!st[4919]) ans++;
if (!st[4991]) ans++;
if (!st[9914]) ans++;
if (!st[9941]) ans++;
if (!st[9419]) ans++;
if (!st[9491]) ans++;
cout<<ans;
return 0;
}
素数环
#include <bits/stdc++.h>
using namespace std;
const int N = 1000000;
/*
全局变量说明:
- n: 输入的整数 n (1 ~ n 的所有数字需要组成素数环).
- ans: 用于存储当前构造的排列环.
- used[i]: 标记数字 i 是否已经被放入环中(default false).
- ok: 用于在最后判断是否输出过解, 若一直为 false 表示无解.
*/
// 数组范围较大以防越界, 但实际 n < 20 即可
int n;
vector<int> ans;
bool used[N];
bool ok; // 是否已经找到或输出过解
// 判断 x 是否为素数的函数
bool check(int x) {
if (x < 2) return false;
for (int i = 2; i * i <= x; i++) {
if (x % i == 0) {
return false;
}
}
return true;
}
/*
dfs(u) 表示已经在 ans 中放入了 u 个数字 (其中 ans[0] = 1 已固定),
接下来要尝试放第 (u+1) 个数字.
*/
void dfs(int u) {
// 如果已经放满 n 个数字, 检查首尾之和是否是素数
if (u >= n) {
int firstnum = ans[0];
int lastnum = ans[n-1];
int sum = firstnum + lastnum;
// 如果首尾之和为素数, 则输出该排列
if (check(sum)) {
ok = true;
for (auto x : ans) {
cout << x << " ";
}
cout << endl;
}
return;
}
// 尝试在位置 u 放所有可用数字 i (1 <= i <= n)
for (int i = 1; i <= n; i++) {
if (!used[i]) {
// 取当前环的最后一个数字与 i 相加, 若为素数才可放置
int sum = ans.back() + i;
if (check(sum)) {
ans.push_back(i);
used[i] = true;
// 继续放下一个位置
dfs(u + 1);
// 回溯, 恢复状态
ans.pop_back();
used[i] = false;
}
}
}
}
int main() {
cin >> n;
// 先将数字 1 放入环 (作为第一个元素), 并标记已用
used[1] = true;
ans.push_back(1);
// 从第 2 个位置开始尝试放数字, 因为第一个位置已固定 1
dfs(1);
// 如果一直没有输出答案, 输出 "No Answer"
if (!ok) {
cout << "No Answer";
}
return 0;
}
找素数(分段筛)
#include <bits/stdc++.h>
using namespace std;
/*
题意: 给定区间 [a, b] (2 ≤ a ≤ b ≤ 2147483647 且 b - a ≤ 1000000),
求其中的素数个数。
算法: "分段筛" (Segmented Sieve)
- 步骤:
1) 先用传统埃氏筛法在 [2, sqrt(b)] 区间内找出所有素数,并存放到 primes 容器中。
2) 在要处理的区间 [a, b] 内,创建一个布尔数组 isPrimeSegment,默认都设为 true。
3) 用前面找到的小素数集合 primes 来标记 [a, b] 区间中的合数。
若 p 为小素数,则从 max(p * p, ceil(a/p)*p) 开始,每隔 p 步将对应位置标记为 false。
4) 统计未被标记的数即为该区间内的素数个数。若 a <= 1,则特判排除 0、1。
复杂度:
- 第1步在 sqrt(b) 内作普通筛,复杂度约 O( sqrt(b) log log sqrt(b) )。
- 第2、3步对区间长 (b - a + 1) 作标记,最多 1,000,001 长度,可行。
注意事项:
- 需要用 64 位类型 (long long 或 int64_t) 存储 b 的开根与下标计算。
- 避免越界。
*/
static const int MAXN = 1000000; // b-a <= 1000000
// 用来存 [2, sqrt(b)] 中所有素数
vector<long long> primes;
// 普通埃氏筛, 获得 [2..upper] 以内所有素数
void getPrimesUpTo(long long upper) {
// 使用简单的布尔数组进行标准筛法
vector<bool> isPrime(upper + 1, true);
isPrime[0] = false;
isPrime[1] = false;
for(long long i = 2; i * i <= upper; i++) {
if(isPrime[i]) {
for(long long j = i * i; j <= upper; j += i) {
isPrime[j] = false;
}
}
}
// 将筛出的素数放到 primes 容器中
for(long long i = 2; i <= upper; i++) {
if(isPrime[i]) {
primes.push_back(i);
}
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
long long a, b;
cin >> a >> b;
// 特判:若区间起点小于 2,将其拉到 2
if(a < 2) a = 2;
// 第 1 步: 筛出 [2, sqrt(b)] 的所有素数
long long limit = (long long)floorl(sqrtl((long double)b));
getPrimesUpTo(limit);
// 第 2 步: 根據区间 [a, b] 建立 isPrimeSegment 数组
long long segmentSize = b - a + 1;
vector<bool> isPrimeSegment(segmentSize, true); // 默认都当作素数
// 第 3 步: 用上一步 primes 标记区间 [a, b] 内的合数
for(long long p : primes) {
if((long long)p * p > b) break; // 超出区间上界则停止
// 找到 [a, b] 区间内, 以 p 为步长需要开始标记的位置
long long start = max((long long)p * p, ( (a + p - 1) / p ) * p);
for(long long x = start; x <= b; x += p) {
isPrimeSegment[x - a] = false;
}
}
// 第 4 步: 统计 isPrimeSegment 中为 true 的个数
long long countPrime = 0;
for(long long i = 0; i < segmentSize; i++) {
if(isPrimeSegment[i]) countPrime++;
}
cout << countPrime << "\n";
return 0;
}
连续素数和
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
using ll = long long;
int primes[N], cnt; // primes[] 用于存储已筛出的素数,cnt 表示素数总数量
bool st[N]; // st[x] 表示 x 是否是合数;true 表示合数,false 表示尚未判断为合数
// 筛出 [2..n] 范围内的所有素数,保存到 primes[],并更新 cnt
void get_primes(int n) {
for (int i = 2; i <= n; i++) {
if (!st[i]) {
primes[cnt++] = i;
}
for (int j = 0; j < cnt && (long long)primes[j] * i <= n; j++) {
st[primes[j] * i] = true;
if (i % primes[j] == 0) {
break;
}
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
// 先筛出 2..N 范围内的素数
get_primes(N);
while (true) {
int n;
cin >> n;
if (!cin || n == 0) {
break;
}
int ans = 0;
// 遍历所有连续子区间 [i..j]
for (int i = 0; i < cnt; i++) {
ll sum = 0;
// 连续累加
for (int j = i; j < cnt; j++) {
sum += primes[j];
if (sum > n) {
// 过大则不必继续累加
break;
}
if (sum == n) {
ans++;
}
}
}
cout << ans << "\n";
}
return 0;
}
小明的素数对
#include <bits/stdc++.h>
using namespace std;
const int N = 100000 + 10;
int primes[N], cnt; // primes数组存储素数,cnt记录素数个数
bool st[N]; // st数组标记是否为素数,st[i] = true 表示 i 不是素数
void get_primes(int n) {
for (int i = 2; i <= n; i++) {
if (!st[i]) primes[cnt++] = i;
for (int j = 0; j < cnt && primes[j] <= n / i; j++) {
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
int main() {
int n;
cin >> n;
get_primes(n);
int ans = 0;
// 遍历 primes 数组,枚举所有的素数对 (p1, p2)
for (int i = 0; i < cnt; i++) {
for (int j = i + 1; j < cnt; j++) {
int diff = primes[j] - primes[i]; // 计算素数对的差
// 检查差 diff 是否在范围内,且为素数
if (2 <= diff && diff <= n && !st[diff]) {
ans++;
}
}
}
cout << ans << endl;
return 0;
}
疑似素数
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6+10;
int primes[N], cnt; // primes数组存储素数,cnt记录素数个数
bool st[N]; // st数组标记是否为素数,st[i] = true 表示 i 不是素数
void get_primes(int n) {
st[1]=true;
st[0]=true;
for (int i = 2; i <= n; i++) {
if (!st[i]) primes[cnt++] = i;
for (int j = 0; j < cnt && primes[j] <= n / i; j++) {
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
int solve(int x)
{
int sum=0;
string s=to_string(x);
for(int i=0;i<s.size();i++)
{
sum+=s[i]-'0';
}
return sum;
}
int main()
{
int n;
cin>>n;
int ans=0;
get_primes(N);
for(int i=2;i<=n;i++)
{
int sum=solve(i);
if(!st[sum])
{
ans++;
}
}
cout<<ans;
return 0;
}
质数拆分
这里,若干两两不同的质数之和,这里其实很容易想到首先我们要求出2019内的所有质数,这个打个表就好了,其次两两不同,我们应该要想到动态规划。
这里设dp[i][j]表示前i个质数,可以两两不同加起来等于j的方案数。
如果当前j>=prime[i],说明当前的质数可以取,取完后我们只要看j-prime[i]有多少种取法,那么方案数之中有部分为dp[i-1][j-prime[i]]种,另外一部分是不取当前的质数prime[i],即dp[i-1][j]。若j<prime[i],说明当前我们取不了prime[i],只有上面两种情况下的后一种即dp[i-1][j]。
其次,我们怎么初始化呢?我们初始化dp[0][0]=1,这样我们就能在第一次质数2的时候初始化了。
#include <iostream>
using namespace std;
const int N=10000;
const int M=2020;
long long dp[M][M];
int primes[N], cnt;
bool st[N];
void get_primes(int n)
{
for (int i = 2; i <= n; i++)
{
if (!st[i]) {
primes[cnt++] = i;
}
for (int j = 0; j < cnt; j++)
{
// 如果当前primes[j]*i超过n,则可跳出循环
long long t = (long long)primes[j] * i;
if (t > n) break;
st[t] = true;
if (i % primes[j] == 0) break;
}
}
}
int main()
{
get_primes(2019);
dp[0][0] = 1;
// i从1开始到cnt,但使用primes[i-1]
for(int i = 1; i <= cnt; i++)
{
int p = primes[i-1];
for(int j = 0; j <= 2019; j++)
{
dp[i][j] = dp[i-1][j];
if(j >= p) {
dp[i][j] += dp[i-1][j - p];
}
}
}
cout << dp[cnt][2019] << endl;
return 0;
}
将dp改为一维数组
#include <iostream>
using namespace std;
static const int M = 2020; // 要覆盖到 2019
long long dp[M];
int primes[10000], cnt;
bool st[10000];
void get_primes(int n)
{
for(int i = 2; i <= n; i++)
{
if(!st[i]) primes[cnt++] = i;
for(int j = 0; j < cnt; j++)
{
long long t = (long long)primes[j] * i;
if(t > n) break;
st[t] = true;
if(i % primes[j] == 0) break;
}
}
}
int main()
{
get_primes(2019);
dp[0] = 1;
// 使用 下标 i ∈ [0, cnt-1],对应第 i 个质数
for(int i = 0; i < cnt; i++)
{
int p = primes[i];
for(int j = 2019; j >= p; j--)
{
dp[j] += dp[j - p];
}
}
cout << dp[2019] << endl;
return 0;
}
纯质数
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=20210605+10;
int primes[N],cnt;
bool st[N];// 标记是否为合数,st[i]=false => i是质数或i=1
void get_primes(int n)
{
for(int i=2;i<=n;i++)
{
if(!st[i])primes[cnt++]=i;
for(int j=0;primes[j]<=n/i;j++)
{
st[primes[j]*i]=true;
if(i%primes[j]==0)break;
}
}
}
bool solve(int x)// 判断整数 x 的每个十进制位是否都是 2,3,5,7
{
while(x>0)
{
int d = x%10;
if(d!=2 && d!=3 && d!=5 && d!=7) {
return false;
}
x/=10;
}
return true;
}
int main()
{
int ans=0;
get_primes(20210605);
for(int i=2;i<=20210605;i++)
{
if(!st[i])// i是质数
{
if(solve(i))
{
ans++;
}
}
}
cout<<ans;
return 0;
}
超级质数
#include <bits/stdc++.h>
using namespace std;
/*
1.10以内的质数有2,3,5,7
2.根据超级质数的组成规则可以组成23,37,53,57,73这样的相邻质数组合,
满足要求的有23,37,53,73
3.根据这些组合可以组成237,373,573,737这样的数字组合,满足要求的只有373
4.根据组成规则已经不能在373的基础之上进行组合,即为最大超级质数
*/
bool is_primes(int x)
{
if(x<2)return false;
for(int i=2;i<=x/i;i++)
{
if(x%i==0)
{
return false;
}
}
return true;
}
int main()
{
// int n;
// while(1)
// {
// cin>>n;
// if(is_primes(n))
// {
// cout<<"是素数"<<endl;
// }
// else
// {
// cout<<"不是素数"<<endl;
// }
// }
cout<<373;
return 0;
}
质数日期
暴力做法
#include <bits/stdc++.h>
using namespace std;
const int N=3e7+10;
using ll=long long;
int primes[N], cnt; // primes[]存储所有素数
bool st[N]; // st[x]存储x是否被筛掉
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) primes[cnt ++ ] = i;
for (int j = 0; primes[j] <= n / i; j ++ )
{
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
int months[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
bool check(int date)
{
int year=date/10000,month=date%10000/100,day=date%100;
if(!month||month>=13||!day)return false;
if(month!=2&&day>months[month])return false;
if(month==2)
{
int leap=(year%4==0&&year%100!=0)||year%400==0;
if(day>28+leap)return false;
}
return true;
}
int main()
{
ll s,t;
cin>>s>>t;
get_primes(t);
int cnt=0;
for(int i=s;i<=t;i++)
{
if(check(i))
{
if(!st[i])
{
cnt++;
}
}
}
cout<<cnt;
return 0;
}
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 100010;
int n;
int f[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
int primes[N],cnt;
bool st[N];
void get_primes(int n)
{
for(int i = 2;i <= n;i++)
{
if(!st[i]) primes[cnt++] = i;
for(int j = 0;primes[j] <= n / i;j++)
{
st[primes[j] * i] = true;
if(i % primes[j] == 0) break;
}
}
}
bool check(int n)
{
if((n % 400 == 0) || (n % 4 == 0 && n % 100 != 0)) return true;
return false;
}
int main()
{
get_primes(N - 1);
LL a,b;
cin >> a >> b;
int res = 0;
for(int i = a / 10000;i <= b / 10000;i++)
{
if(check(i)) f[2] = 29;
else f[2] = 28;
for(int j = 1;j <= 12;j++)
{
for(int k = 1;k <= f[j];k++)
{
LL p = i * 10000ll + j * 100ll + k;
if(p < a) continue;
else if(p > b) break;
else
{
bool flag = true;
for(int l = 0;l < cnt && (LL)primes[l] * primes[l] <= p;l++)
if(p % primes[l] == 0)
{
flag = false;
break;
}
if(flag)
{
res++;
}
}
}
}
}
cout << res << '\n';
return 0;
}
质数游戏2
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10; // 定义素数的最大值
int primes[N], cnt; // primes[] 存储素数,cnt计数素数的数量
bool st[N]; // st[x] 存储 x 是否被筛掉
// 函数用于获取1到 n 的所有素数
void get_primes(int n) {
for (int i = 2; i <= n; i++) {
if (!st[i]) primes[cnt++] = i;
for (int j = 0; primes[j] <= n / i; j++) {
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
int ans; // 计数满足条件的子序列数量
int n; // 序列长度
int a[N]; // 存放数字序列
// DFS函数
void dfs(int u, int cnt, int sum) { // u: 当前遍历位置,cnt:当前子序列数字个数,sum:当前子序列数字和
// 当 u 超过 n 时,表示已遍历完序列
if (u == n + 1) {
// 判断数量和和是否满足条件
if (cnt >= 2 && !st[cnt] && sum >= 2 && !st[sum]) {
ans++;
}
return;
}
// 选择当前数字
dfs(u + 1, cnt + 1, sum + a[u]); // 选择当前数字,增加个数和和
// 不选择当前数字
dfs(u + 1, cnt, sum); // 不选择当前数字,保持不变
}
int main() {
cin >> n;
get_primes(N); // 获取所有素数
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
dfs(1, 0, 0); // 从第一个数字开始DFS
cout << ans;
return 0;
}
魔法阵的能量
#include <bits/stdc++.h>
using namespace std;
/*
思路说明(以代码注释形式呈现):
1. 先读取输入 n,然后判断 n 是奇数还是偶数。
如果 n 是奇数 (n & 1),则直接输出 0 并返回。原因是通过该实现的逻辑认为,
如果 n 为奇数,则没有充分的 2 的因子来与 5 配对形成末尾零。
2. 如果 n 是偶数,将 ans 设为 n/10,表示在此实现中,
先简单认为除以 10 得到其中 2 的数量(实际上并不精准匹配题意,但这是此代码的做法)。
然后再将 n 减少 10 倍(n /= 10)。
3. 循环中使用 e=5,每次将 e *= 5,不断统计 n/e,累加到 ans 中,试图统计 5 的因子总数。
这种统计 5 的方法类似于统计 n! 里 5 的因子个数的方法。
4. 最终打印 ans,作为末尾零的数量。
*/
signed main() {
long long n;
cin >> n;
// 如果 n 是奇数,直接返回 0
if (n & 1) {
cout << 0 << '\n';
return 0;
}
// 将 n 除以 10,试图统计其中的 2 的数量
long long ans = n / 10;
n /= 10;
// 统计 5 的数量
long long e = 5;
while (e <= n) {
ans += n / e;
e *= 5;
}
// 最终打印累加值 ans
cout << ans << '\n';
return 0;
}
阿坤老师切割年糕
解题思路
大致题意:
给定一个整数n,把n分为两个整数,如果这两个整数存在一个是另一个的因数,输出加1,求n中有多少个这样的输出
一般思路 :
n为输入的值
x + (n-x) = n
(n-x) % x == 0时计数器加1
可行但时间复杂度大
枚举因数 :
由题意x + y =n 已知x是y的因数 即c * x = y, c为大于0的任意常数
则x + y = x + c*x = x(c + 1 ) = = n
枚举x
一个数的因数只用遍历到此数的开根号,例如100,只需遍历1~10,这样就可以大大提升效率,具体原理就自己百度一下;
如果n % x == 0, 即存在一个整数c + 1 使(c +1)* x = n, 结果加2
为什么加2,当n%x == 0时,x是n的因数,n/x也是n的因数
例如n=4时,x的实际范围是1~2,当x=1时,4%1 = 0,4/1=4,1和4都是n的因数 所以加2
如果n // x = x,结果加1(因为两个数是一样的,加1即可)
当x=2时,x=n/x=2,加1即可
最后,我们会发现因数不能等于n,例如x=4时,1+4=5已经大于n了,故cnt要减1
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin>>n;
int cnt=0;
for(int i=1;i<=n/i;i++)
{
if(n%i==0)
{
if(n/i==i)
{
cnt+=1;
}
else
{
cnt+=2;
}
}
}
cout<<cnt-1;
return 0;
}
阶乘分解
阶乘的约数和
#include <bits/stdc++.h>
using namespace std;
const int mod = 998244353; // 模数
// 定义一个全局 map,用于存储质因数及其对应的次数
map<int, long long> mp;
// 对整数 x 进行质因数分解,并将其质因数及对应的次数存入 mp 中
void solve(int x) {
for (int i = 2; i <= x / i; i++) {
int cnt = 0;
while (x % i == 0) { // 如果 i 是 x 的一个因子
cnt++;
x /= i;
}
if (cnt) mp[i] += cnt; // 如果 i 是质因数,记录其次数
}
// 如果 x 本身是一个大于 1 的质数
if (x > 1) mp[x]++;
}
int main() {
int n;
cin >> n;
// 对 2 到 n 的所有整数进行质因数分解
for (int i = 2; i <= n; i++) solve(i);
long long sum = 1; // 用于存储约数之和的结果
// 遍历所有质因数及其次数
for (auto& [p, c] : mp) {
long long ret = 0, t = 1;
// 计算当前质因数 p 的约数和部分 (1 + p + p^2 + ... + p^c) % mod
for (int i = 0; i <= c; i++) {
ret = (ret + t) % mod; // 累加当前项
t = t * p % mod; // 计算下一项 (p^i)
}
// 将当前质因数的约数和乘入结果
sum = sum * ret % mod;
}
cout << sum << endl;
return 0;
}
程序的输出零
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, res = 0;
cin >> n;
// 思路:需要计算f(n)的末尾有多少个零。
// 尾数为0的数字来源于因子2和5的乘积,且因子5的数量通常比因子2少。
// 所以,我们主要计算f(n)中因子5的数量。
// 由于f(n) = n * f(n-2),相当于每隔2个数乘一次,只考虑偶数情况。
if (n % 2 == 0) { // 如果n是偶数
for (int i = n; i > 1; i = i - 2) { // 从n开始每隔2个数递减
int temp = i;
while (temp % 5 == 0) { // 计算当前数中包含多少个因子5
res++; // 每找到一个因子5,结果加1
temp /= 5; // 除以5继续检查
}
}
}
cout << res << endl; // 输出结果
return 0;
}
双子数
#include <iostream>
using namespace std;
const int N = 4e6 + 10;
int primes[N], cnt;
bool st[N];
void get_primes(int n) {
for (int i = 2; i <= n; i++) {
if (!st[i]) primes[cnt++] = i;
for (int j = 0; primes[j] <= n / i; j++) {
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
int main() {
int ans = 0;
get_primes(N); // 获取所有小于等于N的素数
// 遍历所有素数对(p, q)
for (int i = 0; i < cnt; i++) {
for (int j = 0; j < i && 1LL * primes[i] * primes[j] * primes[i] * primes[j] <= 23333333333333LL; j++) {
// 检查p和q的乘积是否在给定范围内
if (1LL * primes[i] * primes[j] * primes[i] * primes[j] >= 2333) {
++ans;
}
}
}
cout << ans; // 输出结果
return 0;
}
躲炮弹
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
int n,L,R;
int ans;
bool check(int x)// 枚举x的所有因数
{
for(int i=1;i<=x/i;i++)
{
if(x%i==0)
{
if(L<=i&&i<=R)return true;// 如果因数i或x/i在[L, R]范围内,则返回true
if(L<=x/i&&x/i<=R)return true;
}
}
// 如果没有因数在[L, R]范围内,则返回false
return false;
}
int main()
{
cin>>n>>L>>R;
// 如果初始位置n在[L, R]范围内
if(L<=n&&n<=R)
{
int ans = n - L + 1; // 初始位置到L的距离
for(int i=1;i<=2000;i++) // 枚举从R开始向右最多2000步的距离,寻找一个合法位置
{
if(!check(R+i))
{
ans=min(ans,R+i-n);// 更新最小步数
break;
}
}
cout<<ans<<endl;
return 0;
}
else // 如果初始位置n不在[L, R]范围内
{ // 枚举从n向左右最多2000步的距离,寻找一个合法位置
for(int i=0;i<=2000;i++)
{
if(!check(n+i)||!check(n-i))
{
cout<<i<<endl;
break;
}
}
}
return 0;
}
GCD+LCM
互质数的个数
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MOD = 998244353;
// 快速幂算法,计算 a^b % c
ll qmi(ll a, ll b, ll c) {
ll ans = 1;
a %= c;
while (b) {
if (b & 1) {
ans = (ans * a) % c;
}
a = (a * a) % c;
b >>= 1;
}
return ans;
}
int main() {
int a, b;
cin >> a >> b;
ll mi = qmi(a, b, MOD); // 计算 a^b % MOD
ll ans = 0;
// 统计与 mi 互质的数的个数
for (int i = 1; i < mi; i++) {
if (__gcd((ll)i, mi) == 1) ans++;
}
cout << ans;
return 0;
}
等差数列
等差数列
a
n
=
a
1
+
(
n
−
1
)
d
a_n=a_{1}+(n-1)d
an=a1+(n−1)d
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
int a[100000];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
sort(a+1,a+1+n);
int d=0;
for(int i=2;i<=n;i++)
{
d=__gcd(d,a[i]-a[i-1]);
}
if(d==0)
{
cout<<n<<endl;
}
else cout<<(a[n]-a[1])/d+1;
return 0;
}
Hankson 的趣味题
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
int lcm(int a,int b)
{
return a/__gcd(a,b)*b;
}
int main()
{
int n;
cin>>n;
while(n--)
{
int a0,a1,b0,b1;
cin>>a0>>a1>>b0>>b1;
int ans=0;
for(int x=1;x<=sqrt(b1);x++)
{
if(b1%x==0)
{
if(__gcd(x,a0)==a1&&lcm(x,b0)==b1)ans++;
int y=b1/x;
if(x==y)
{
continue;
}
if(__gcd(y,a0)==a1&&lcm(y,b0)==b1)ans++;
}
}
cout<<ans<<endl;
}
return 0;
}
核桃的数量
#include <bits/stdc++.h>
using namespace std;
/*
题目思路:
1. 我们需要确保每个开发组(共3个)分到的核桃数量相同,并且能够被各组人数整除。
2. 这意味着每袋核桃数量是 a、b、c 三个数的最小公倍数 (LCM)。
3. 先计算 a 和 b 的 LCM,再与 c 的 LCM 进行计算,得到所有人的最小公倍数即为答案。
*/
int lcm(int x, int y) {
// 计算两个数的最小公倍数,公式:LCM(a, b) = (a * b) / GCD(a, b)
return x / std::gcd(x, y) * y;
}
int main() {
int a, b, c;
cin >> a >> b >> c; // 从输入读取 a, b, c
int num = lcm(lcm(a, b), c); // 先求 a 和 b 的 LCM,再与 c 一起求 LCM
cout << num;
return 0;
}
奇偶比例
#include <bits/stdc++.h>
using namespace std;
/*
解题思路:
1. 使用前缀数组 preodd 和 preven 分别统计前 i 个数中的奇数和偶数个数,方便快速获取任意区间的奇偶数目。
2. 定义函数 getpair(l, r) 返回子区间 [l..r] 的奇偶数最简比例;将末端区间 [i..n] 的比例出现次数记录在哈希表中,并且在遍历时累加前端区间 [1..i-1] 所对应的相同比例出现次数。
*/
const int N = 1e5 + 7;
using PII = pair<int,int>;
// 存储奇偶数量前缀和
int a[N], preodd[N], preven[N];
// 计算最大公约数
int gcd(int x, int y) {
return y == 0 ? x : gcd(y, x % y);
}
// 计算区间 [l..r] 的奇偶数最简比例
PII getpair(int l, int r) {
int oddCount = preodd[r] - preodd[l - 1];
int evenCount = preven[r] - preven[l - 1];
// 若全部为偶数
if (oddCount == 0) {
return {0, 1};
}
// 若全部为奇数
else if (evenCount == 0) {
return {1, 0};
}
// 否则计算奇偶数的最简比
else {
int g = gcd(oddCount, evenCount);
return {oddCount / g, evenCount / g};
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> a[i];
// 更新奇数前缀和与偶数前缀和
preodd[i] = preodd[i - 1] + (a[i] % 2);
preven[i] = preven[i - 1] + (a[i] % 2 == 0);
}
// 哈希表统计 每种奇偶最简比例出现的次数
map<PII, int> mp;
long long cnt = 0;
// 从后往前遍历,记录 [i..n] 这段的奇偶最简比例出现次数
// 并累加和 [1..i-1] 区间有相同最简比例的次数
for(int i = n; i > 1; i--) {
mp[getpair(i, n)]++;
cnt += mp[getpair(1, i - 1)];
}
cout << cnt << "\n";
return 0;
}
旅行I
最大公约数 (gcd) 的特性是,它能找到多个步长之间的最大公共步长。如果我们没有包含所有国家对 x 的影响,那我们可能会错过一些可行的移动步长 D。
#include <bits/stdc++.h>
using namespace std;
/*
思路注释:
1. 首先对国家坐标进行排序,方便确定最小和最大位置。
2. 将移动距离 D 决定为 gcd(...),即 x 与每个国家坐标之间差值的最大公约数(若某个差值为0则修正为1),
这样可以保证在固定步长 D 下依然可以到达所有国家。
3. 根据小蓝初始坐标 x 所处的位置关系(可能在最右、在最左或在中间),
分别计算在步长为 D 时从 x 覆盖到所有国家所需的最少移动次数。
*/
int gcd(int a, int b) {
while (b != 0) {
int tmp = a;
a = b;
b = tmp % b;
}
return a;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, x;
cin >> n >> x;
vector<int> a(n);
for(int i = 0; i < n; i++) {
cin >> a[i];
}
// 对所有国家坐标排序
sort(a.begin(), a.end());
// 初始化 d 为 |a[0] - x|
int d = abs(a[0] - x);
if(d == 0) {
d = 1; // 避免 gcd(0,0) 的情况
}
// 计算 x 与其他国家之间坐标差值的最大公约数
for(int i = 0; i < n - 1; i++) {
d = gcd(d, abs(a[i + 1] - x));
}
int ans = 0;
if(x >= a[n - 1]) {
// 若 x 已经在最右侧,计算从 x 到最左侧坐标的距离 (x-a[0]),再除以步长 d
ans = (x - a[0]) / d;
} else if(x > a[0]) {
// 若 x 在中间位置,则尝试向左/向右覆盖全部国家,取覆盖最大的距离
if(a[n - 1] - x > x - a[0]) {
ans = ((x - a[0]) + (a[n - 1] - a[0])) / d;
} else {
ans = ((a[n - 1] - x) + (a[n - 1] - a[0])) / d;
}
} else {
// 若 x 在最左侧,计算从 x 到最右侧 a[n-1] 的距离 (a[n-1]-x),再除以 d
ans = (a[n - 1] - x) / d;
}
cout << ans << endl;
return 0;
}
寻找她
#include <iostream>
using namespace std;
/*
思路说明:
1. 首先读取 n,即可买到的地铁票种数,并分别读入每种票的价格 a[i] 以及可移动站数 b[i]。
2. 为了保证能到达所有地铁站,需要选择两张票的步数 b[i], b[j] 满足 gcd(b[i], b[j])=1,
因为只有当步数之间的最大公约数为 1 时,才能组合出任意距离覆盖所有站点。
3. 使用两重循环检索任意两张地铁票,若它们的 b 值满足 gcd(b[i], b[j])=1,则更新最小花费。
4. 如果遍历结束都找不到 gcd=1 的票对,则输出 -1,否则输出最小花费。
*/
#define ll long long
// 计算最大公约数
ll gcd(ll a, ll b) {
if (b > a) {
ll t = a;
a = b;
b = t;
}
if (b == 0) {
return a;
} else {
return gcd(b, a % b);
}
}
int main() {
ll n;
cin >> n;
ll a[n], b[n];
// 读入价格数组
for (int i = 0; i < n; i++) {
cin >> a[i];
}
// 读入可移动站数数组
for (int i = 0; i < n; i++) {
cin >> b[i];
}
// 初始化最小花费(先取前两张票价格之和)
ll money = a[0] + a[1];
int flag = 0; // 标记是否能找到 gcd=1 的票对
// 检索两两票组合,寻找 gcd=1
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (gcd(b[i], b[j]) == 1) {
flag = 1;
money = min(money, a[i] + a[j]);
}
}
}
// 若没找到满足 gcd=1 的任意票对,则输出 -1
if (flag == 0) {
cout << -1;
return 0;
}
// 否则输出最小花费
cout << money;
return 0;
}
小咕咕
#include <bits/stdc++.h>
using namespace std;
int lcm(int a,int b)
{
return a/__gcd(a,b)*b;
}
int main()
{
int a,b,t;
cin>>a>>b>>t;
int sum=lcm(a,b);
int ans=t/sum;
cout<<ans;
return 0;
}
最小生成树
kruskal
#include <bits/stdc++.h>
using namespace std;
// 定义常量 N,最大节点数和最大边数
const int N = 100010;
// 定义变量
int n, m, res, cnt; // n为节点数,m为边数,res为最小生成树的总权重,cnt为选择的边数
int fa[N]; // fa数组用来记录并查集的父节点
struct edge
{
int x, y, w; // x和y表示边的两个端点,w表示边的权重
} edges[N * 2]; // edges数组存储所有的边
// 边的排序规则,按照边的权重升序排列
bool cmp(edge a, edge b)
{
return a.w < b.w;
}
// 查找并查集的根节点(路径压缩)
int findset(int x)
{
if (fa[x] == x)
{
return x; // 如果x是自己的父节点,直接返回
}
fa[x] = findset(fa[x]); // 递归查找并压缩路径
return fa[x];
}
// Kruskal算法的核心部分
int kruskal()
{
// 按照边的权重升序排序所有的边
sort(edges + 1, edges + m + 1, cmp);
// 初始化并查集,fa[i]表示i的父节点,初始时每个节点的父节点是它自己
for (int i = 1; i <= n; i++)
{
fa[i] = i;
}
res = 0; // 最小生成树的权重
cnt = 0; // 记录选择的边数
// 遍历所有的边,选择不形成环的边加入最小生成树
for (int i = 1; i <= m; i++)
{
int x = edges[i].x, y = edges[i].y, w = edges[i].w; // 当前边的两个端点和权重
// 查找边的两个端点的根节点
int fx = findset(x), fy = findset(y);
// 如果两个端点属于同一集合,说明加入该边会形成环,跳过
if (fx == fy)
{
continue;
}
// 否则将两个集合合并
fa[fx] = fy;
// 加入这条边到最小生成树中
res += w;
cnt++;
}
return res;
}
int main()
{
// 输入节点数n和边数m
cin >> n >> m;
// 输入所有的边,存储在edges数组中
for (int i = 1; i <= m; i++)
{
int a, b, c;
cin >> a >> b >> c;
edges[i] = {a, b, c};
}
// 调用kruskal算法,计算最小生成树的权值
kruskal();
// 如果选择的边数小于n-1,则说明图不能连通,输出impossible
if (cnt < n - 1)
{
cout << "impossible";
return 0;
}
// 输出最小生成树的权重
cout << res;
return 0;
}
修建公路1
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const ll N=3e5+10;
ll n,m,res,cnt;
ll fa[N];
struct edge
{
ll u,v,w;
}edges[N*2];
bool cmp(edge a,edge b)
{
return a.w<b.w;
}
ll findset(ll x)
{
if(fa[x]==x)
{
return x;
}
fa[x]=findset(fa[x]);
return fa[x];
}
ll kruskal()
{
sort(edges+1,edges+1+m,cmp);
for(ll i=1;i<=n;i++)
{
fa[i]=i;
}
res=0;
cnt=0;
for(ll i=1;i<=m;i++)
{
ll x=edges[i].u,y=edges[i].v,w=edges[i].w;
ll fx=findset(x),fy=findset(y);
if(fx==fy)
{
continue;
}
fa[fx]=fy;
res+=w;
cnt++;
}
return res;
}
int main()
{
cin>>n>>m;
for(ll i=1;i<=m;i++)
{
ll a,b,c;
cin>>a>>b>>c;
edges[i]={a,b,c};
}
kruskal();
if(cnt<n-1)
{
cout<<-1;
return 0;
}
cout<<res;
return 0;
}
城市规划大师
#include <bits/stdc++.h>
using namespace std;
const int N = 1000 + 10; // 对应 n 最大值,城市数量上限
const int MAXE = 1000 * 1000; // 边数量上限,根据 n*(n-1)/2 估计
int n, k;
int fa[N]; // 并查集数组
bool st[N][N]; // 用于标记是否可建边
pair<int,int> p[N]; // 城市坐标
struct edge {
int x, y, w;
} edges[MAXE];
bool cmp(const edge &a, const edge &b) {
return a.w < b.w;
}
int findset(int x) {
return fa[x] == x ? x : (fa[x] = findset(fa[x]));
}
// Kruskal 生成 MST
int kruskal(int edgeCount) {
// 将所有边按权值升序排序
sort(edges, edges + edgeCount, cmp);
// 并查集初始化
for(int i = 1; i <= n; i++) {
fa[i] = i;
}
int res = 0;
int usedEdges = 0; // 记录已选边的数量
for(int i = 0; i < edgeCount; i++) {
// 如果该边原本就不宜建设,则跳过
if(!st[edges[i].x][edges[i].y]) continue;
int fx = findset(edges[i].x);
int fy = findset(edges[i].y);
// 若未联通,则选此边并合并集合
if(fx != fy) {
fa[fx] = fy;
res += edges[i].w;
usedEdges++;
// 若已经选够 n-1 条边,则可提前结束
if(usedEdges == n - 1) break;
}
}
return res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> k;
for(int i = 1; i <= n; i++) {
cin >> p[i].first >> p[i].second;
}
// 预处理所有可建设的边,并计算曼哈顿距离
int edgeCount = 0;
for(int i = 1; i <= n; i++) {
for(int j = i + 1; j <= n; j++) {
edges[edgeCount].x = i;
edges[edgeCount].y = j;
edges[edgeCount].w = abs(p[i].first - p[j].first)
+ abs(p[i].second - p[j].second);
// 初始标记为可建
st[i][j] = true;
st[j][i] = true;
edgeCount++;
}
}
// 读入不宜建公路的 k 对城市关系,将对应标记置为 false
for(int i = 1; i <= k; i++) {
int u, v;
cin >> u >> v;
st[u][v] = false;
st[v][u] = false;
}
// 运行 Kruskal 算法,输出最小建设成本
cout << kruskal(edgeCount) << "\n";
return 0;
}
买礼物
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e6 + 10;
// 全局变量:
// a 表示单个商品价格
// b 表示商品数量
// res 表示最终结果(最少花费)
// cnt 表示边的数量,后边用于循环并构建边
long long a, b, res, cnt;
// 并查集数组
long long fa[N];
// 边结构体,存储两个商品以及优惠价 w
struct edge {
long long x, y, w;
} edges[N * 2];
// 比较函数,用于排序 edges,按优惠价从小到大排序
bool cmp(edge a, edge b) {
return a.w < b.w;
}
// 并查集查找函数,找到 x 所属的集合代表
long long findset(long long x) {
if (fa[x] == x) {
return x;
}
// 路径压缩
fa[x] = findset(fa[x]);
return fa[x];
}
// 使用 Kruskal 算法来构建最小生成树(实际上是对“优惠”和“单买”间的选择)
// 思路:试图将商品通过“优惠边”相连,如果优惠券小于单独买的价格 a,就用优惠价,否则用 a
// 同时保证每个连通分量都要加上至少一次 a(因为第一个被买的商品无优惠)
long long kruskal() {
// 1. 将 edges 按照优惠价 w 排序
sort(edges + 1, edges + 1 + cnt, cmp);
// 2. 初始化并查集
for (long long i = 1; i <= b; i++) {
fa[i] = i;
}
// 3. 遍历所有边,尝试合并集合
for (long long i = 1; i <= cnt; i++) {
long long x = edges[i].x;
long long y = edges[i].y;
long long w = edges[i].w;
// 找到 x, y 的父节点
long long fx = findset(x);
long long fy = findset(y);
// 如果在同一集合,说明已经连通,不需要再次合并
if (fx == fy) {
continue;
}
// 合并集合
fa[fx] = fy;
// 如果优惠价小于单买价格,则 res += w;否则 res += a
if (w < a) {
res += w;
} else {
res += a;
}
}
// 4. 统计还有多少个商品并未与其他商品联通(即自己是一个集合代表)
// 这些商品没有任何优惠,需要单独花 a
for (long long i = 1; i <= b; i++) {
if (fa[i] == i) {
res += a;
}
}
return res;
}
int main() {
// 输入 a, b
cin >> a >> b;
// 读取优惠矩阵 K_{I,J}
// 由于优惠矩阵是对称的,只需要在 i <= j 时记录即可
for (long long i = 1; i <= b; i++) {
for (long long j = 1; j <= b; j++) {
long long x;
cin >> x;
// 只在 i <= j 并且 x != 0 时才记录边
// x = 0 表示没有优惠
if (i <= j && x != 0) {
cnt++;
edges[cnt].x = i;
edges[cnt].y = j;
edges[cnt].w = x;
}
}
}
// 调用 Kruskal 算法,计算最小花费
kruskal();
// 输出结果
cout << res;
return 0;
}
拆地毯
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e6 + 10;
// n 表示关键区域的数量,m 表示地毯(边)的数量,k 表示最多能保留的地毯条数
// res 用于存储结果(美丽度总和),cnt 记录当前选取的地毯数
long long n, m, k, res, cnt;
// 并查集数组 fa,用于表示所有关键区域的集合关系
long long fa[N];
// 自定义结构体存储边:x 与 y 为边的两个端点,w 是边的美丽度
struct edge {
long long x, y, w;
} edges[N * 2];
// 比较函数,按边的美丽度从大到小排序(因为要尽量取美丽度大的边)
bool cmp(edge a, edge b) {
return a.w > b.w;
}
// 并查集查找函数,找出元素 x 所在集合的根节点(路径压缩)
long long findset(long long x) {
if (fa[x] == x) {
return x;
}
fa[x] = findset(fa[x]);
return fa[x];
}
// kruskal 函数使用类似 Kruskal 的思路,
// 但由于要最大化美丽度,所以在排序时按降序排序边
// 然后依次判断能否将当前边加到生成树中,直到选满 k 条或边耗尽
long long kruskal() {
// 1. 将所有边按美丽度从大到小排序
sort(edges + 1, edges + 1 + m, cmp);
// 2. 初始化并查集
for (long long i = 1; i <= n; i++) {
fa[i] = i;
}
// 3. 依次枚举排序后的边
for (long long i = 1; i <= m; i++) {
long long x = edges[i].x;
long long y = edges[i].y;
long long w = edges[i].w;
long long fx = findset(x);
long long fy = findset(y);
// 如果这条边的两个顶点不在同一集合,就可以选取,否则会产生环
if (fx != fy) {
// 合并两个集合
fa[fx] = fy;
// 将此边的美丽度加入总和
res += w;
cnt++;
// 只需要保留最多 k 条地毯,达到 k 条就可以停止
if (cnt == k) {
break;
}
}
}
return res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
// 输入关键区域数 n,边数 m,最多允许的边数 k
cin >> n >> m >> k;
// 读入每条地毯的端点和美丽度
for (int i = 1; i <= m; i++) {
int u, v, w;
cin >> u >> v >> w;
edges[i].x = u;
edges[i].y = v;
edges[i].w = w;
}
// 调用 kruskal 函数,输出结果
kruskal();
cout << res << "\n";
return 0;
}
局域网
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
// n 表示计算机的数量,k 表示网线数量
// res 存储最终保留的网线畅通度之和(本代码中是用来累加被选中的边)
// cnt 记录已选网线条数
// fa[] 是并查集数组,用于判断两台计算机是否已经在同一个连通分量中
const int N = 1e6 + 10;
int n, k, res, cnt;
int fa[N];
// 定义边结构体:x 和 y 为该边连接的两台计算机,w 表示该边的畅通程度
struct edge {
int x, y, w;
} edges[N * 2];
// 比较函数,按照边的畅通程度从大到小进行排序
// 因为本代码想优先选择大畅通度的边
bool cmp(edge a, edge b) {
return a.w > b.w;
}
// 并查集“查找”函数:寻找 x 所在集合的根节点(带路径压缩)
int findset(int x) {
if (fa[x] == x) {
return x;
}
fa[x] = findset(fa[x]);
return fa[x];
}
// kruskal 函数:对 edges 数组进行排序后,从大到小选取边,
// 如果当前边连接的两台计算机不在同一集合,就将它们合并,否则跳过
int kruskal() {
// 将边按 w 从大到小排序
sort(edges + 1, edges + 1 + k, cmp);
// 初始化并查集
for (int i = 1; i <= n; i++) {
fa[i] = i;
}
// 遍历所有边,若不在同一集合则合并,并把该边的畅通度记入 res
for (int i = 1; i <= k; i++) {
int x = edges[i].x;
int y = edges[i].y;
int w = edges[i].w;
int fx = findset(x);
int fy = findset(y);
// 若在同一集合中,代表选此边会成环,跳过
if (fx == fy) {
continue;
}
// 否则合并集合,并累加 w
fa[fx] = fy;
res += w;
cnt++;
}
// 返回保留的边的畅通度总和
return res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
// 读入 n、k
cin >> n >> k;
// 后续需要计算所有网线的总畅通度 sum,用于最终求出被删掉的网线总畅通度
int sum = 0;
for (int i = 1; i <= k; i++) {
int u, v, w;
cin >> u >> v >> w;
edges[i].x = u;
edges[i].y = v;
edges[i].w = w;
sum += w;
}
// 构建最大生成树后,res 中存储了选中的边畅通度之和
kruskal();
// sum - res 表示被移除的网线(形成回路或未选中)的畅通度总和
cout << sum - res << "\n";
return 0;
}
聪明的猴子
[P2504 HAOI2006] 聪明的猴子 - 洛谷
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
/*
题目思路:
1. 首先会读入 M(猴子数)、以及每只猴子的最大跳跃距离。
2. 接着读入 N(树的数量)以及每棵树的坐标。
3. 计算每两棵树之间的欧几里得距离并将其作为边(edges)存储。边的权值为两点间取整后的距离(题中w为int)。
4. 使用 Kruskal 算法构造最小生成树(MST),遍历将所有树连通时所选用边的最大值称为 max_distance —— 这代表想要遍历所有树必须要能跨越的最大单次跳跃距离。
5. 最后统计有多少只猴子的最大跳跃距离 >= MST 的最大边,输出这个数量。
*/
// m: 猴子数量, n: 树的数量
// res: 最终可在所有树上觅食的猴子数量
// idx: 边的数量
// max_distance: MST 中出现的最大边长
int m, n, res, idx;
int monkey[N]; // 存储每只猴子的最大跳跃距离
pair<int,int> point[N]; // 存储树的坐标
int fa[N]; // 并查集数组
int max_distance; // Kruskal 找到的最大边长
// 边结构体
struct edge {
int x, y, w; // x,y 为树编号, w 表示两树间距离(取整)
} edges[N * 2];
// 排序规则:从小到大
bool cmp(edge a, edge b) {
return a.w < b.w;
}
// 计算两点(x1, y1) 与 (x2, y2) 之间的欧几里得距离(之后取 int)
double distance_xy(int x1, int y1, int x2, int y2) {
// 返回两点距离
return sqrt((double)(x1 - x2)*(x1 - x2) + (double)(y1 - y2)*(y1 - y2));
}
// 并查集查找函数(带路径压缩)
int findset(int x) {
if(fa[x] == x) {
return x;
}
fa[x] = findset(fa[x]);
return fa[x];
}
// Kruskal 算法 - 找到最小生成树的同时记录 MST 中的最大边长
int kruskal() {
// 将所有边按距离从小到大排序
sort(edges + 1, edges + 1 + idx, cmp);
// 初始化并查集
for(int i = 1; i <= n; i++) {
fa[i] = i;
}
// 遍历所有边,从小到大尝试加入 MST
for(int i = 1; i <= idx; i++) {
int x = edges[i].x;
int y = edges[i].y;
int w = edges[i].w;
// 找到 x, y 所在集合代表
int fx = findset(x);
int fy = findset(y);
// 若已经在同一集合,则跳过(加上会成环)
if(fx == fy) {
continue;
}
// 否则把这条边加入 MST
fa[fx] = fy;
// 更新最大边长度
max_distance = max(max_distance, w);
}
return max_distance;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
// 读入猴子数量
cin >> m;
// 读入每只猴子的最大跳跃距离
for(int i = 1; i <= m; i++) {
cin >> monkey[i];
}
// 读入树的数量
cin >> n;
// 读入每一棵树的坐标
for(int i = 1; i <= n; i++) {
cin >> point[i].first >> point[i].second;
}
// 枚举所有树对,计算距离并存为边
for(int i = 1; i <= n; i++) {
for(int j = i + 1; j <= n; j++) {
double dist = distance_xy(point[i].first, point[i].second,
point[j].first, point[j].second);
// 取整后存入边
edges[++idx] = { i, j, (int)dist };
}
}
// Kruskal 算法构建 MST,找到 MST 中的最大边长
kruskal();
// 判断哪些猴子的跳跃距离 >= MST 最大边即可在所有树之间穿梭
for(int i = 1; i <= m; i++) {
if(monkey[i] >= max_distance) {
res++;
}
}
// 输出可在所有树上觅食的猴子数
cout << res << endl;
return 0;
}
繁忙的都市
[P2330 SCOI2005] 繁忙的都市 - 洛谷
#include <bits/stdc++.h>
using namespace std;
// 题目思路:
// 1. 我们需要在所有道路中挑选若干条,使所有交叉路口保持连通。
// 2. 在保证连通的前提下,优先选取最少的道路,即最终选出的道路数应为 (n-1) 条。
// 这是构建最小生成树 (MST) 的基本性质。
// 3. 在满足以上条件后,还要求所选道路中分值最大值最小,即需要使用类似 Kruskal 的最小生成树算法
// 按边权从小到大排序,贪心地选边,每次若不形成环则加入 MST,
// 最终选中 n-1 条边后,记录选中的最大分值。
// 4. 输出选出的道路数 (应为 n - 1) 和在 MST 中的最大边分值。
const int N = 1e6 + 10;
// n:交叉路口数量,m:道路数量
// cnt:当前已选道路数,用于统计 MST 中的边数应为 n-1
// res:记录已选道路中最大的边权值(即分值)
int n, m, cnt, res;
// 并查集数组 fa
int fa[N];
// 边结构体:x, y 表示连接的两个路口,w 表示分值
struct edge {
int x, y, w;
} edges[N * 2];
// 边比较规则:按分值从小到大排序
bool cmp(edge a, edge b) {
return a.w < b.w;
}
// 并查集查找函数:找到 x 的根节点
int findset(int x) {
if (fa[x] == x) {
return x;
}
fa[x] = findset(fa[x]);
return fa[x];
}
// Kruskal 算法构建最小生成树并记录所选边的最大分值
int kruskal() {
// 1. 将所有边按分值从小到大排序
sort(edges + 1, edges + 1 + m, cmp);
// 2. 初始化并查集,每个路口自成一个集合
for (int i = 1; i <= n; i++) {
fa[i] = i;
}
// 3. 依次遍历排好序的边,若不形成环,则合并,并更新最大分值
for (int i = 1; i <= m; i++) {
int x = edges[i].x;
int y = edges[i].y;
int w = edges[i].w;
int fx = findset(x);
int fy = findset(y);
// 若在同一集合中,加入会成环,跳过
if (fx == fy) {
continue;
}
// 合并两个集合
fa[fx] = fy;
// 更新最大边权
res = w;
// 记录已选边数量
cnt++;
// 当选出的边数达到 n - 1 时,可提前结束
if (cnt == n - 1) {
break;
}
}
return res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
// 读入交叉路口数 n 和道路数 m
cin >> n >> m;
// 读入每条道路的信息 (两个路口编号与分值)
for (int i = 1; i <= m; i++) {
int x, y, w;
cin >> x >> y >> w;
edges[i] = { x, y, w };
}
// 调用 Kruskal 算法构建 MST,并获取选中边的最大分值
kruskal();
// 输出选出的道路数量 n-1 与选中道路的最大分值
cout << (n - 1) << " " << res << "\n";
return 0;
}