目录

CC看简介吧2

C/C++:看简介吧= ̄ω ̄=(2)

1.朋友数

题目描述:

给定两个整数x,y ( 1 ≤ x ≤ y ≤ 1 0 9 ) (1\le x \le y \le 10^9) (1≤x≤y≤109),若它们满足下列等式,则称其为朋友数:

( y − x ) m o d    10

( y + x ) m o d    10 (y - x) \mod 10 = (y + x) \mod 10 (y−x)mod10=(y+x)mod10

现给定两个参数x,y,每次可任选如下操作之一:

  • 将x增加1或减少1;
  • 将y增加1或减少1。

请在保证操作后仍满足 ( 1 ≤ x ≤ y ≤ 1 0 9 ) (1\le x \le y \le 10^9) (1≤x≤y≤109)的前提下,计算将 ( x , y ) (x,y) (x,y)变成朋友数所需的最少操作次数。

【名词解释】
朋友数:若整对 ( x , y ) (x,y) (x,y) 满足 ( y − x ) m o d    10

( y + x ) m o d    10 (y - x) \mod 10 = (y + x) \mod 10 (y−x)mod10=(y+x)mod10 ,则称其为朋友数。

输入描述:

每个测试文件均包含多种测试数据。
第一行输入一个整数 T   ( 1 ≤ T ≤ 1 0 4 ) T\ (1\le T \le 10^4) T (1≤T≤104) ,代表数据组数;
随后 T T T 行,每行输入两个整数 x , y   ( 1 ≤ x ≤ y ≤ 1 0 9 ) x,y\ (1\le x \le y \le 10^9) x,y (1≤x≤y≤109) ,表示查询参数。

输出描述:

对于每一组测试数据,新起一行,输出一个整数,表示将所给 ( x , y ) (x,y) (x,y) 变为朋友数的最少操作次数

示例1:
输入:

3
4 9
8 9
15 25

输出:

1
3
0

解析:

将给的等式条件化简:
等价于 ( y − x )

( y + x )         ( m o d    10 ) (y - x) = (y + x) \ \ \ \ \ \ \ (\mod 10) (y−x)=(y+x)       (mod10)
再化简也就是 2 x

0         ( m o d    10 ) 2x=0 \ \ \ \ \ \ \ (\mod 10) 2x=0       (mod10)
最后得到 x

0         ( m o d    5 ) x=0 \ \ \ \ \ \ \ (\mod 5) x=0       (mod5)

  • 也就是这个等式成立要求就是 x x x 是5的倍数,那么 x x x 的个位数必定是0或5
  • 注意题目要求操作后 x , y x,y x,y仍要满足 ( 1 ≤ x ≤ y ≤ 1 0 9 ) (1\le x \le y \le 10^9) (1≤x≤y≤109)

参考代码:

  1. 从 x x x 的个位数必定是0或5来考虑:
#include <iostream>
#include <climits>//需要<climits>来使用INT_MAX
#include <algorithm>
using namespace std;

int solve() {
    int x, y;
    cin >> x >> y;
    int c = x % 10;
    if (c == 0 || c == 5) {
        return 0;
    }

    int a[2] = {0, 5};
    int res = INT_MAX; // 初始化为最大值

    for (int d : a) {
        // x增加操作
        int s1 = (d - c) % 10;
        if (s1 < 0) s1 += 10;

        int dx = x + s1;
        int ops1;
        if (dx <= y) {
            ops1 = s1;
        } else {
            ops1 = s1 + (dx - y);
        }

        // x减少操作
        int s2 = (c - d) % 10;
        if (s2 < 0) s2 += 10;

        int ops2 = INT_MAX; // 初始化为最大值(表示不可行)
        if (x - s2 >= 1) {
            ops2 = s2;
        }

        // 取当前候选值的最小操作次数
        int current_min = min(ops1, ops2);
        // 更新总体最小值
        res = min(res, current_min);
    }

    return res;
}

int main() {
    int t;
    cin >> t;
    while (t--) {
        int res = solve();
        cout << res << endl;
    }
    return 0;
}
  1. 从 x x x 是5的倍数出发:
#include <iostream>
#include <climits>//需要<climits>来使用INT_MAX
using namespace std;
int solve()
{
    int x,y;
    cin>>x>>y;
    if(x%5==0)
    {
        return 0;
    }
    //找到前后两个5的倍数
    int t1=(x/5)*5;//前一个5的倍数
    int t2=t1+5;   //后一个5的倍数
    //计算减少到t1的步数(注意需要确保t1>=1,当x<5时,t1是小于1的)
    int d1=x-t1;
    if(t1<1) {//由于题目限制x不能小于1,故而这条路走不通了,把d1设到最大
        d1=INT_MAX;
    }
    //计算增加到t2的步数(当t2>y时,那还需要调整下y)
    int d2=t2-x;
    int ops=0;//y需要调整的步数
    if(t2>y)
    {
        ops=t2-y;
    }
    int res=min(d1,d2+ops);//最后比较那边步数小返回小的
    return res;
}
int main() {
    int t;
    cin>>t;
    while(t--){
        int res=solve();
        cout<<res<<endl;
    }
}

2.损坏的键盘

题目描述:

程序员 T k Tk Tk 在办公时频繁使用键盘,使得不同按键因使用频率不同而出现磨损。对于 26 26 26 个英文字母按键,按照字母顺序,第 i i i 个按键每次按下会打印出 ai 个该字母。
给定一个长度为 n n n 的仅包含小写字母的字符串 s s s,请判断 T k Tk Tk 是否能够打印出该字符串:能则输出 Y e s Yes Yes,否则输出 N o No No.

输入描述:

单个测试文件包含多组测试数据。
第一行输入一个整数 T ( 1 ≤ T ≤ 1 0 4 ) T(1 \le T \le 10^4) T(1≤T≤104),表示测试组数

  • 第一行输入一个整数 n ( 1 ≤ n ≤ 2 × 1 0 5 ) n(1 \le n \le 2 \times10^5) n(1≤n≤2×105) ,表示字符串长度。
  • 第二行输入 26 26 26 个整数 a 1 , a 2 , … , a 26 ( 0 ≤ a i ≤ 10 ) a_1,a_2,\ldots,a_{26} (0 \le a_i \le 10) a1​,a2​,…,a26​(0≤ai​≤10) ,分别表示敲击 a ∼ z a \sim z a∼z 对应键时每次打印的字符数量
  • 第三行输入一个长度为 n n n 的仅包含小写字母的字符串 s s s。
    题目保证单个测试文件 n n n 的和不超过 2 × 1 0 5 2 \times 10^5 2×105 。

输出描述:

对于每组测试数据,输出一行:能打印出字符串时输出 Y e s Yes Yes,否则输出 N o No No 。

示例1:
输入:

2
6
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
abbcdz
3
1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0
aab

输出:

Yes
No

解析:

  • 题目意思也就是将字符串 s s s 分解成若干段,每一段是相同的字母组成,且段的长度必须是对应字母按键的整数倍

参考代码:

#include <iostream>
using namespace std;
void solve() {
    int n;
    cin >> n;
    int letter[26];
    for (int i = 0; i < 26; i++) {
        cin >> letter[i];
    }
    string s;
    cin >> s;
    int flag = 1;
    for (int i = 0; i < n; i++) {
        // 如果该字母的按键值为0,直接失败
        if (letter[s[i] - 'a'] == 0) {
            flag = 0;
            break;
        }
        //往后遍历,开始分段
        int j = i + 1, num = 1;
        for (; j < n; j++) {
            if (s[i] != s[j]) {
                break;
            }
            num++;
        }
        //判断该段是否为对应按键值的倍数
        if (num % letter[s[i] - 'a']) {
            flag = 0;
            break;
        }
        i = j - 1;
    }
    if (flag)
        cout << "Yes" << endl;
    else
        cout << "No" << endl;
}
int main() {
    int t;
    cin >> t;
    while (t--)
        solve();
}

一样的,下面的整洁些:

#include <iostream>
using namespace std;

void solve() {
    int n;
    cin >> n;
    int letter[26];
    for (int i = 0; i < 26; i++) {
        cin >> letter[i];
    }
    string s;
    cin >> s;

    int i = 0;
    while (i < n) {
        char c = s[i];
        int idx = c - 'a';

        // 如果该字母的按键值为0,直接失败
        if (letter[idx] == 0) {
            cout << "No" << endl;
            return;
        }

        // 计算连续相同字母的长度
        int count = 1;
        int j = i + 1;
        while (j < n && s[j] == c) {
            count++;
            j++;
        }

        // 检查长度是否能被letter[idx]整除
        if (count % letter[idx] != 0) {
            cout << "No" << endl;
            return;
        }

        i = j;  // 移动到下一个不同字符的位置
    }

    cout << "Yes" << endl;
}

int main() {
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

3.包包的XOR

题目描述

包包有一个长度 n n n 的数组 a a a 和一个非负整数 k k k。
定义对数组 a a a 的一次操作为:

  • 对于数组 a a a 中的每个元素 a i a_i ai​,更新 a i

    a i ⊕ k a_i = a_i \oplus k ai​=ai​⊕k,其中 ⊕ \oplus ⊕ 代表按位异或运算。
  • 在更新后的数组 a a a 中移除最小的元素。如果存在多个最小元素,仅移除一个。
    注意到每次操作后,数组 a a a 的长度会减少1
    现在包包想让你求出:在恰好 n − 1 n - 1 n−1 次操作后, a a a 中剩余的唯一元素的值。

输入描述:

第一行输入两个正整数 n , k   ( 1 ≤ n ≤ 2 ⋅ 1 0 5 , 0 ≤ k ≤ 1 0 9 ) n,k\ (1 \le n \le 2 \cdot 10^5 , 0 \le k \le 10^9) n,k (1≤n≤2⋅105,0≤k≤109) ,分别表示数组 a a a 的长度以及每次更新时异或的常数
第二行输入 n n n 个整数 a 1 , a 2 , … , a n   ( 0 ≤ a i ≤ 1 0 9 ) a_1,a_2,\ldots ,a_n\ (0 \le a_i \le 10^9) a1​,a2​,…,an​ (0≤ai​≤109) ,表示 a a a 中的元素。

输出描述:

输出一个整数,代表在 n − 1 n - 1 n−1 次操作后数组 a a a 中剩余的唯一元素的值。

示例1:

输入:

3 5
1 2 3

输出:

3

解析:

  • 每次操作对整个数组 ⊕   k \oplus \ k ⊕ k ,然后移除最小的元素
  • 异或操作是线性的,异或两次等于不变,
  • 于是可以创建两个数组来模拟这个操作,一个为偶数步下的数组,一个为奇数步下的数组,交替地选择最小值移除,另外创建一个数组来记录被移除的元素,

参考代码:

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;
void solve() {
    int n, k;
    cin >> n >> k;
    //a为原始数组,也就是奇数步的数组
    vector<int>a(n, 0);
    //b为异或后的,是偶数步数组
    vector<int>b(n, 0);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
        b[i] = a[i] ^ k;
    }

    //记录整个过程,被移除的元素标记为1
    vector<int> ans(n, 0);

    //创建排序索引
    vector<int> idx_a(n), idx_b(n);
    iota(idx_a.begin(), idx_a.end(), 0);
    iota(idx_b.begin(), idx_b.end(), 0);

    //按原始值的从小到大排序索引
    sort(idx_a.begin(), idx_a.end(), [&](int i, int j) {
        return a[i] < a[j];
    });

    //按异或值的从小到大排序索引
    sort(idx_b.begin(), idx_b.end(), [&](int i, int j) {
        return b[i] < b[j];
    });
    //标记当前未被移除的最小元素索引
    int index_a = 0, index_b = 0;

    //模拟n-1次已出货操作
    for (int i = 0; i < n - 1; i++) {
        if (i % 2 == 0) {
            //偶数步:在异或后的空间中移除最小值
            while (index_b < n && (ans[idx_b[index_b]] == 1))
                index_b++;
            ans[idx_b[index_b]] = 1;
        } else {
            //奇数步:在原始空间中移除最小值
            while (index_a < n && (ans[idx_a[index_a]] == 1))
                index_a++;
            ans[idx_a[index_a]] = 1;
        }
    }

    //找到唯一剩余的元素
    for (int i = 0; i < n; i++) {
        if (ans[i] == 0) {
            if (n % 2)
                cout << a[i] << endl;
            else
                cout << b[i] << endl;
        }
    }
}
int main() {
    solve();
}
// 64 位输出请用 printf("%lld")

4.小C的无限循环小数

题目描述:

小C是一位数学爱好者,他想知道在给定分子 p p p ,在限定分母不超过 q q q 的情况下,在分数 p i \frac{p}{i} ip​ 在 k k k 进制中有多少个是无限循环小数。

  • 有限小数:有限小数是指一个分数在某一进制下的小数表示能够在有限位后结束。例如,在十进制中, 1 2

    0.5 \frac{1}{2} = 0.5 21​=0.5 是一个有限小数。
  • 无线循环小数:无限循环小数是指一个分数在某一进制下的小数表示既不终止且从某一点开始呈现周期性重复。例如:在十进制中, 1 3

    0.3 \frac{1}{3} = 0.3 31​=0.3 就是一个无限循环小数。

输入描述:

第一行输入三个整数 p , q , k   ( 2 ≤ p , q , k ≤ 1 0 14 ) p,q,k\ (2 \le p,q,k \le 10^{14}) p,q,k (2≤p,q,k≤1014) ,

  • p p p 表示分子;
  • q q q 表示分母上限;
  • k k k 表示进制。

输出描述:

输出一个整数,表示在进制 k k k 下,对于所有分母 i i i 满足 1 ≤ i ≤ q 1 \le i \le q 1≤i≤q ,分数 p i \frac{p}{i} ip​ 是无限循环小数的个数

示例1:
输入:

6 10 10

输出:

2

示例2:
输入:

7 7 2

输出:

3

解析:

  • 在k进制下,分数 p i \frac{p}{i} ip​ 是有限小数 ⇔ 约分后的分母 i ′ i’ i′ 的所有质因子都包含在k的质因子中
  • 先处理分子p,移除 p 中与 k 共有的质因子,从而保证后续的互质条件
  • 获取k的因子
  • 获取处理后的p的因子,
  • 根据容斥原理,我们就通过遍历处理后的p的因子,因为对于分母i,可以分解为 i

    d × i ′ i=d \times i’ i=d×i′ ,计算在 1 到 i ′ i’ i′ 范围内,质因子完全在 k k k 的因子中的数的个数

参考代码:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long ll;

// 计算最大公约数
ll gcd(ll a, ll b) {
    while (b) {
        a %= b;
        swap(a, b);
    }
    return a;
}

// 对x进行质因数分解,返回去重后的质因子列表
vector<ll> get_prime(ll x) {
    vector<ll> fact;
    // 1和以下的数没有质因子
    if (x <= 1)
        return fact;
    
    // 单独处理2(唯一的偶质数)
    if (x % 2 == 0) {
        fact.push_back(2);
        while (x % 2 == 0)
            x /= 2;
    }
    
    // 处理奇数质因子
    for (ll i = 3; i * i <= x; i += 2) {
        if (x % i == 0) {
            fact.push_back(i);
            while (x % i == 0)
                x /= i;
        }
    }
    
    // 如果最后x>1,说明x本身就是一个质数
    if (x > 1)
        fact.push_back(x);
    return fact;
}

// 容斥原理计算函数
// primes: k的所有质因子列表
// index: 当前处理的质因子在primes中的索引
// y: 当前剩余的可选数值上限
ll F(const vector<ll>& primes, int index, ll y) {
    // 递归终止条件:已经处理完所有质因子
    if (index == primes.size())
        return 1;  // 返回1表示空选择(数1)
    
    ll res = 0;
    ll current = y;
    
    // 容斥原理的核心循环
    while (current > 0) {
        // 递归处理:不选择当前质因子的情况
        res += F(primes, index + 1, current);
        // 选择当前质因子:将current除以当前质因子
        // 这样下次循环会处理包含当前质因子的情况
        current /= primes[index];
    }
    
    return res;
}

void solve() {
    ll p, q, k;
    cin >> p >> q >> k;
    
    // 移除p中与k共有的质因子
    ll temp = p;
    ll d;
    do {
        d = gcd(temp, k);
        temp /= d;
    } while (d != 1);  // 直到没有公有质因子为止
    
    // 获取k的所有质因子
    vector<ll> primes = get_prime(k);
    
    ll ans = q;  // 初始假设所有分母都对应无限小数
    
    // 获取处理后的p的所有因子
    vector<ll> divs;
    for (ll i = 1; i * i <= temp; i++) {
        if (temp % i == 0) {
            divs.push_back(i);
            if (i != temp / i) {
                divs.push_back(temp / i);
            }
        }
    }
    
    // 对于p的每个因子,应用容斥原理
    for (ll d : divs) {
        if (d > q)  // 如果因子d已经大于q,跳过
            continue;
        
        ll limit = q / d;  // 计算i'的最大可能值
        if (limit == 0)    // 如果limit为0,跳过
            continue;
        
        // 减去满足条件的i'的个数(这些对应有限小数)
        ans -= F(primes, 0, limit);
    }
    
    cout << ans << endl;  // 输出无限小数的个数
}

int main() {
    solve();
    return 0;
}