P3955 [NOIP 2017 普及组] 图书管理员

记录43

#include<bits/stdc++.h>
using namespace std;
int f(int len){
	int a=1;
	while(len--) a*=10;
	return a;
}
int main(){
	int n,q,a[1010]={},len,m;
	cin>>n>>q;
	for(int i=0;i<n;i++) cin>>a[i];
	sort(a,a+n);
	while(q--){
		bool flag=1;
		cin>>len>>m;
		for(int i=0;i<n;i++){
			int t=a[i]%f(len);
			if(t==m){
				cout<<a[i]<<endl;
				flag=0;
				break;
			}
		}
		if(flag) cout<<-1<<endl;
	}
	return 0;
} 

题目传送门https://www.luogu.com.cn/problem/P3955


突破点

如果一本书的图书编码恰好以读者的需求码结尾,那么这本书就是这位读者所需要的

👉处理数字后几位

如果存在第 i 个读者所需要的书,则在第 i 行输出第 i 个读者所需要的书中图书编码最小的那本书的图书编码,否则输出 −1。

👉数字有大小顺序


思路

  1. 写一个可以处理指定后几位数的函数
  2. 将图书编码进行排序
  3. 寻找结尾符合的数字编码

代码简析

#include<bits/stdc++.h>
using namespace std;
int f(int len){
	int a=1;
	while(len--) a*=10;
	return a;
}
int main(){
	int n,q,a[1010]={},len,m;
	cin>>n>>q;
	for(int i=0;i<n;i++) cin>>a[i];
	sort(a,a+n);
	...
	return 0;
} 

f 函数  👉  处理编码的尾巴

说明:数字的尾巴就是后几位,%10得到后一位,%100得到后两位

a 数组  👉 存图书编码

sort()函数  👉 图书编码进行一个从小到大排序

#include<bits/stdc++.h>
using namespace std;
int f(int len){
	int a=1;
	while(len--) a*=10;
	return a;
}
int main(){
	int n,q,a[1010]={},len,m;
	cin>>n>>q;
	for(int i=0;i<n;i++) cin>>a[i];
	sort(a,a+n);
	while(q--){
		bool flag=1;
		cin>>len>>m;
		for(int i=0;i<n;i++){
			int t=a[i]%f(len);
			if(t==m){
				cout<<a[i]<<endl;
				flag=0;
				break;
			}
		}
		if(flag) cout<<-1<<endl;
	}
	return 0;
} 

while循环 👉  对每个长度还有结尾进行输入

flag 👉 默认没找到

for循环 👉 输入后对所有的图书编码的后几位进行一个比较

                   找到后输出,然后修改flag并退出不用比较了,接着输入下一个数据

注意:

#include<bits/stdc++.h>
using namespace std;
int main(){
	int n,q,a[1010]={},len,m;
	cin>>n>>q;
	for(int i=0;i<n;i++) cin>>a[i];
	sort(a,a+n);
	while(q--){
		bool flag=1;
		cin>>len>>m;
		for(int i=0;i<n;i++){
			int t=a[i]%int(pow(10,len));//pow返回double类型
			if(t==m){
				cout<<a[i]<<endl;
				flag=0;
				break;
			}
		}
		if(flag) cout<<-1<<endl;
	}
	return 0;
} 

如果使用pow函数,必须进行int强转,因为不能对小数取余,而pow函数返回double类型


补充

CSP-J快速幂函数完全指南


1. 为什么需要快速幂?

问题背景:计算 2^503^1000000 时,暴力连乘会超时且溢出。

暴力法缺陷

// 计算a^n,循环n次,O(n)复杂度
long long pow_slow(long long a, long long n) {
    long long res = 1;
    for (int i = 0; i < n; i++) res *= a;  // n=10^9时,循环10^9次,必超时!
    return res;
}

CSP-J数据范围n ≤ 10^9,暴力法完全不可行。


2. 快速幂核心思想:二进制拆分

数学原理:利用幂的乘方性质,将指数 n 按二进制拆分。

a^13 = a^(1101)₂ = a^(8) × a^(4) × a^(1)

关键观察

  • 每次将指数折半,底数平方

  • 只乘当指数二进制位为1时的底数

  • 时间复杂度:**O(log n) **,计算 a^10^9 仅需30次循环!


** 3. 迭代实现(CSP-J首选)**

#include <bits/stdc++.h>
using namespace std;

// 快速幂模板:计算 a^n % mod
long long qpow(long long a, long long n, long long mod) {
    long long res = 1;  // 结果初始化为1
    a %= mod;           // 防止底数过大
    while (n) {         // 当指数不为0
        if (n & 1) {    // 如果n的二进制末位是1(奇数)
            res = res * a % mod;  // 乘到结果中
        }
        a = a * a % mod;        // 底数平方
        n >>= 1;                // 指数右移一位(折半)
    }
    return res;
}

// 示例:计算 2^10 mod 1000000007
int main() {
    long long ans = qpow(2, 10, 1e9+7);  // ans = 1024
    cout << ans << endl;  // 输出1024
    return 0;
}

代码解析

  • n & 1:判断指数奇偶性(等价于 n % 2 == 1

  • n >>= 1:指数折半(等价于 n /= 2

  • a = a * a % mod:底数平方并取模,防止溢出


4. 递归实现(易懂但稍慢)

long long qpow(long long a, long long n, long long mod) {
    if (n == 0) return 1;           // 边界:a^0 = 1
    long long half = qpow(a, n / 2, mod); // 递归计算a^(n/2)
    half = half * half % mod;       // 平方
    if (n & 1) half = half * a % mod;   // 如果n是奇数,多乘一个a
    return half;
}

CSP-J建议用迭代,递归深度log n可能栈溢出(n=10^9时深度约30,安全但迭代更快)。


5. 复杂度分析

方法时间复杂度循环次数(n=10^9)CSP-J适用性
暴力连乘O(n)10^9次❌ 必超时
快速幂O(log n)30次✅ 完美

空间复杂度:O(1),只使用常数变量。


6. CSP-J典型应用场景

场景1:大数幂取模(最常见)

// 题目:计算 a^b mod 1337 (a,b ≤ 10^9)
long long ans = qpow(a, b, 1337);
场景2:斐波那契数列(矩阵快速幂入门)

// 求F(n) mod 1e9+7,n ≤ 10^18
// 用矩阵快速幂:[[1,1],[1,0]]^n = [[F(n+1),F(n)],[F(n),F(n-1)]]
// CSP-S内容,J组只需知道快速幂可扩展到矩阵
场景3:模逆元

// 求a在模p下的逆元(a与p互质)
// a^(-1) ≡ a^(p-2) (mod p)  (费马小定理)
long long inv = qpow(a, mod-2, mod);  // mod是质数

7. 注意事项与竞赛陷阱

陷阱1:不取模导致溢出

// ❌ 错误:中间乘法可能溢出long long
res = res * a;  // res和a都可能接近1e9,乘积可达1e18,long long最大9e18,风险!

// ✅ 正确:每次乘法后立即取模
res = res * a % mod;
a = a * a % mod;
陷阱2:mod=1的特殊情况

qpow(a, n, 1);  // 任何数mod 1都是0,直接返回0
陷阱3:底数为0且指数为0

// 0^0数学上无定义,通常返回1
if (a == 0 && n == 0) return 1;  // 根据题目要求处理

8. CSP-J标准模板(直接背诵)

// 万能快速幂模板(支持mod=1的情况)
long long qpow(long long a, long long n, long long mod) {
    if (mod == 1) return 0;  // 任何数mod 1都是0
    long long res = 1;
    a %= mod;
    while (n) {
        if (n & 1) res = (res * a) % mod;
        a = (a * a) % mod;
        n >>= 1;
    }
    return res;
}

// 快速乘(防止乘法溢出,当mod接近1e18时使用)
// 原理:a*b % mod = (a*b - (long long)((long double)a*b/mod)*mod + mod) % mod
long long qmul(long long a, long long b, long long mod) {
    a %= mod; b %= mod;
    long long res = 0;
    while (b) {
        if (b & 1) res = (res + a) % mod;
        a = (a + a) % mod;
        b >>= 1;
    }
    return res;
}

9. 如何快速验证你的快速幂?

测试用例

// 测试1:基本功能
qpow(2, 10, 1000) == 1024 % 1000 == 24

// 测试2:大指数
qpow(2, 1000000000, 1000000007) // 结果在合理范围

// 测试3:mod=1
qpow(123, 456, 1) == 0

// 测试4:指数为0
qpow(100, 0, 1000) == 1

// 测试5:底数为0
qpow(0, 10, 1000) == 0

10. 快速幂vs pow函数

#include <cmath>

// ❌ pow函数问题多
double res = pow(2, 100);  // 1. 结果为double,有精度误差
                           // 2. 无取模功能
                           // 3. 速度慢10倍

// ✅ 快速幂优势
long long res = qpow(2, 100, mod);  // 1. 精确整数
                                    // 2. 支持取模
                                    // 3. O(log n)速度

竞赛建议永远不要在大数运算中使用pow(),一律用快速幂。


11. 核心要点总结(背诵版)

  1. 目的:解决大指数幂运算的超时溢出问题

  2. 核心:二进制拆分,a^n = a^(n/2) × a^(n/2) × (a 若n为奇数)

  3. 模板:迭代版比递归版快,务必背熟

  4. 三要素res=1, while(n), n&1判断

  5. 防溢出:每次乘法后立即 % mod,不能用pow()

  6. 复杂度:时间O(log n),空间O(1),循环次数≈30次(当n=10^9)

  7. 应用:大数幂取模、模逆元、矩阵快速幂(进阶)

竞赛箴言:遇到 a^b mod pb>10^5,直接上快速幂模板,这是CSP-J选手的基本素养。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值