首页 > 其他分享 >信息学奥赛复赛复习18-CSP-J2022-01解密-二分答案、二分找边界、二分时间复杂度、二分求最小

信息学奥赛复赛复习18-CSP-J2022-01解密-二分答案、二分找边界、二分时间复杂度、二分求最小

时间:2024-10-17 19:00:06浏览次数:6  
标签:二分 01 int 18 sqrt long 4pq dt

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;
}

标签:二分,01,int,18,sqrt,long,4pq,dt
From: https://www.cnblogs.com/myeln/p/18472897

相关文章

  • 20241016
    intarr[3]={10,20,30};int*parr=arr;1.*parr、*arr分别代表什么   *(parr+0)==*(arr+0)==10==》取首素值=========================================================================      2.*(parr+1)、*(arr+1)、*parr+1、*arr+1分别......
  • 《程序员修炼之道:从小工到专家》读书笔记 01
    编程原则与最佳实践编程原则DRY(Don'tRepeatYourself):避免重复代码。通过抽象和封装来提高代码的复用性,减少维护成本。KISS(KeepItSimple,Stupid):强调简洁性。程序越简单,出错的可能性越小,理解和维护也越容易。YAGNI(YouAren'tGonnaNeedIt):不要过早地为未来的需求设计复......
  • CitrixSQL Server 2016高可用之SQL镜像 SQL Server mirror 带见证服务器
    CitrixSQLServer2016高可用之SQL镜像SQLServermirror带见证服务器原来写过SQL-2008的镜像教程,时过境迁,现在流行2016了,当然也是因为自己常常用到这个功能,写下来SQL的镜像方法帮助不会的朋友。这个教程对于SQL2008\2012\2016以及2017都是可用的。三台SQL服务器,都安装好SQL软......
  • CitrixWindows SQL Server2016安装教程 SQL管理工具SSMS安装
    CitrixWindowsSQLServer2016安装教程SQL管理工具SSMS安装   ......
  • 产品开发01-socket编程
    实验目的:根据提供的TCP协议代码完成具有相关功能的UDP代码的编写,并成功实现功能客户端要求:(1) 客户端可以多次向服务器发送数据;(2) 客户端能够接收服务器端发送数据,并回显正确;服务器端要求:(1) 运行端口可配置(2) 将客户端发送来的消息正确显示,并将该消息发送给客户端;(3) 支持多个客......
  • 1601 添加运算符 枚举 递归dfs
    #include<bits/stdc++.h>usingnamespacestd;typedeflonglongll;constintN=1e3+10,inf=0x3f3f3f3f;inta[N],vis[N];intn,ans;//计算函数:根据运算符i对sum和a[x]进行运算intcal(intsum,inti,intx){if(i==1)returnsum+a[x];//加法......
  • [题解]P1311 [NOIP2011 提高组] 选择客栈
    P1311[NOIP2011提高组]选择客栈P6032选择客栈加强版只要\([l,r]\)区间之内存在一个\(i\)使得\(w[i]\lep\),这个区间就是符合条件的。所以我们遍历每一个元素\(i\),根据贪心的思想我们维护\([1,i]\)区间内满足\(w[i]\lep\)的最大\(i\),记为\(mp\)。对于每个元素\(i\),寻找\(......
  • 5:安装配置 Oracle18C
    5:安装配置Oracle18C安装需求如下: 安装完成。  总结:    数据库名称为Oracletest      表空间 spacetest    账号:spacetest    密码:spacetest    表:spacetast删除数据库上面的步骤走完是卸......
  • OpenGL: 计算机图形学OpenGL在Visual Studio 2019/2022中的环境配置
    前言    在查找了众多有关OpenGL相关的环境配置后,对opengl在vs中的初步配置终是有了收获,总结作以此篇以免自己遗忘,也希望对大家有所帮助。一、OpenGL简介        OpenGL(OpenGraphicsLibrary)是一个跨语言、跨平台的应用程序编程接口(API),用于渲染二维和三维......
  • 2024.010.10
    今天主要是记录一些问题,主要是在使用vue通过axios发送请求的时候,起初我认为需要把vue和springboot整合到一块但是在听完课之后发现并不需要,因为vue的目的是实现前后端分离的开发,因此两个项目可以同时运行,调整vue项目就是改前端代码,调整springboot就是改后端代码,前端只负责发送请......