PDF文档回复:20241017
1 P8814 [CSP-J 2022] 解密
[题目描述]
给定一个正整数 k,有 k 次询问,每次给定三个正整数 ni,ei,di,求两个正整数 pi,qi,使 ni=pi×qi、ei×di=(pi−1)(qi−1)+1
[输入格式]
第一行一个正整数 k,表示有 k 次询问。
接下来 k 行,第 i 行三个正整数 ni,di,ei
[输出格式]
输出 k 行,每行两个正整数 pi,qi 表示答案。
为使输出统一,你应当保证 pi≤qi。
如果无解,请输出 NO
[输入输出样例]
输入 #1
10
770 77 5
633 1 211
545 1 499
683 3 227
858 3 257
723 37 13
572 26 11
867 17 17
829 3 263
528 4 109
输出 #1
2 385
NO
NO
NO
11 78
3 241
2 286
NO
NO
6 88
说明/提示
数据规模
以下记 m=n−e×d+2
保证对于 100% 的数据,1≤k≤10^5,对于任意的 1≤i≤k,1≤ni≤10^18,1≤ei×di≤ 10^18,1≤m≤ 10^9
2 相关知识点
二分答案
二分答案顾名思义,它用二分的方法枚举答案,并且枚举时判断这个答案是否可行
直接对答案进行枚举查找,接着判断答案是否合法。如果合法,就将答案二分进一步靠近,如果不合法,就接着二分缩小判断。这样就可以大大的减少时间。
二分中有时可以得可行得答案,但不是最大的,继续向右靠近,求出最大值
int ans = 1;
int l = 1,r = 100000;//在1~100000之间的整数枚举
while(l <= r){
int m = l + (r - l) / 2;
if(check(m)){//满足 则进行向右缩小范围 看看有没有更大的
ans = m;//可能多次赋值 最后一定是可能的最大值
l = m + 1;
}else{//不满足缩小边长 向左缩小范围 用更小边长继续尝试
r = m - 1;
}
}
二分找边界
//左闭右闭 while left right 最终left=right+1
while(left<=right) left = mid + 1; right =mid-1;
//左闭右开 while left right 最终left=right
while(left<right) left = mid + 1; right =mid;
//左开右闭 while left right 最终left=right
while(left<right) left=mid; right=mid-1;
//左开右开 while left right 最终left=right-1
while(left+1<right) left=mid; right=mid;
二分查找时间复杂度
二分查找每次都缩小或扩大为原来的一半,所以也是Olog(n)
3 思路分析
基本公式推导
根据题目给出条件对基本公式进行推导
n=p * q
e * d = (p-1)*(q-1)+1
=p*q-(p+q)+1+1
=n-(p+q)+2
上面式子整理可得
p+q=n-e*d+2
思路1
1多次询问,每次询问暴力计算p和q
2 根据条件和推导已知p*q和p+q计算p和q的值,从1枚举到p+q的值
3 如果p*q也符合,则输出
示例程序
#include<bits/stdc++.h>
using namespace std;
int k;//k次询问
long long n,d,e,m;//n d e 3个整数 m 为p+q的值
int main(){
cin>>k;//输入k次询问
for(int i=0;i<k;i++){//k次询问
cin>>n>>d>>e;//输入n d e
m=n-e*d+2;//根据公式推导m为p+q p+q=n-e*d+2
int p=-1;//默认p为-1
for(int i=1;i<m;i++){//从1枚举到p+q
if(i*(m-i)==n){//如果i为p m-i为q,判断p*q为n,满足另外一个条件 找到p 退出
p=i;
break;
}
}
if(p!=-1){//如果找到 输出
cout<<p<<" "<<m-p<<endl;
}else{//找不到输出NO
cout<<"NO"<<endl;
}
}
return 0;
}
暴力枚举可得50%分数
思路1-二分优化1
1 在思路1基础上,二分枚举,时间复杂度从n变成logn
2 使用等值比较,可能超大的2个数比较大的那个,输出时需要特殊处理
#include<bits/stdc++.h>
using namespace std;
int k;
long long n,d,e,m;
int main(){
cin>>k;
for(int i=0;i<k;i++){
cin>>n>>d>>e;
m=n-e*d+2;//p+q
int L=1,R=m,p=-1;//1~m
while(L<=R){//前后都是闭区间
int mid=L+(R-L)/2;
if(mid * (m-mid)==n){//如果相同 找到退出
p=mid;
break;
}else if(mid * (m-mid)>n){
R=mid-1;
}else{
L=mid+1;
}
}
if(p!=-1){
if(p>m-p){//找到的p有可能是比较大的数,如果是需要交换输出
p=m-p;
}
cout<<p<<" "<<m-p<<endl;
}else{
cout<<"NO"<<endl;
}
}
return 0;
}
思路1-二分优化2
在上面二分的基础上,找到最小的那个数,保证二分找到的就是比较小的数
通过如下代码,如果相等也继续缩小范围找
if(mid * (m-mid)>=n){
示例代码
#include<bits/stdc++.h>
using namespace std;
int k;
long long n,d,e,m;
int main(){
cin>>k;
for(int i=0;i<k;i++){
cin>>n>>d>>e;
m=n-e*d+2;//p+q
int L=1,R=m;
while(L<R){
int mid=L+(R-L)/2;
if(mid * (m-mid)>=n){
R=mid;
}else{
L=mid+1;
}
}
if(R * (m-R) ==n){
cout<<R<<" "<<m-R<<endl;
}else{
cout<<"NO"<<endl;
}
}
return 0;
}
思路2-数学推导
数学推导
前面推导数
p+q=n-e*d+2
已知 p*q=n
根据完全平方公式
(p+q)^2=p^2+2pq+q^2 (1)
(p-q)^2=p^2-2pq+q^2 (2)
(1)-(2)得
(p+q)^2-(p-q)^2
=p^2+2pq+q^2-(p^2-2pq+q^2)
=4pq
(p-q)^2=(p+q)^2-4pq
p-q=sqrt((p+q)^2-4pq)
或
p-q=-sqrt((p+q)^2-4pq)
我们假设p和q这2个数中,q为比较小的数,所以p-q不能为负数
p-q=sqrt((p+q)^2-4pq) (3)
又
p+q=n-e*d+2 (4)
(3)-(4)得
2p=n−ed+2-sqrt((p+q)^2-4pq)
p=(n−ed+2-sqrt((p+q)^2-4pq))/2 (5)
(4)-(5)得
q=n-e*d+2-(n−ed+2-sqrt((p+q)^2-4pq))/2
=(n−ed+2+sqrt((p+q)^2-4pq))/2
示例代码
#include<bits/stdc++.h>
using namespace std;
int k;
long long n,d,e;
long long m,dt,r;//m为p+q
int main(){
cin>>k;
for(int i=0;i<k;i++){
cin>>n>>d>>e;
m=n-e*d+2;//p+q
dt=m*m-4*n;//(p+q)^2-4pq
r=sqrt(dt);//sqrt((p+q)^2-4pq)
/*
dt为sqrt的值,不能小于0
r * r!=dt,dt开方为整数
(m-r)%2==1,(n−ed+2-sqrt((p+q)^2-4pq))/2 ,p和q必须为整数
上面任意条件不满足都是无解
*/
if(dt<0 || r * r!=dt || (m-r)%2==1){
cout<<"NO"<<endl;
}else{
cout<<(m-r)/2<<" "<<(m+r)/2<<endl;
}
}
return 0;
}