必做题
P8682 [蓝桥杯 2019 省 B] 等差数列
快速过一下题目:求包含已知数列的最小等差数列。
假设这个最小等差数列的公差为
d
d
d :
对于已知数列
A
1
,
A
2
,
⋯
,
A
N
A_1,A_2,\cdots,A_N
A1,A2,⋯,AN 有
A
i
−
A
i
−
1
=
n
∗
d
(
1
≤
i
≤
N
,
n
=
0
,
1
,
2
,
3
⋯
)
A_i-A_{i-1}=n*d (1\le i \le N,n =0,1,2,3 \cdots)
Ai−Ai−1=n∗d(1≤i≤N,n=0,1,2,3⋯)
故此题为求数列
A
1
,
A
2
,
⋯
,
A
N
A_1,A_2,\cdots,A_N
A1,A2,⋯,AN 的最大公约数
对于整数 ( a , b , c ) (a,b,c) (a,b,c) 设 g c d ( a , b ) , g c d ( a , b , c ) gcd(a,b),gcd(a,b,c) gcd(a,b),gcd(a,b,c) 为 ( a , b ) , ( a , b , c ) (a,b),(a,b,c) (a,b),(a,b,c)的最大公约数,自然有 g c d ( a , b , c ) = g c d ( g c d ( a , b ) , c ) gcd(a,b,c) = gcd(gcd(a,b),c) gcd(a,b,c)=gcd(gcd(a,b),c)
将数组排好序之后,求出所有相邻项差的最大公约数
// #include<algorithm>
sort(arr, arr+n);
d = arr[1] - arr[0];
for(int i=2 ; i<n ; i++){
d = __gcd(d, arr[i] - arr[i-1]);
}
完整代码如下:
#include<iostream>
#include<algorithm>
using namespace std;
/*
// 也可以手搓一个gcd函数
int gcd(int a, int b)
{
if(b==0) return a;
return gcd(b, a%b);
}
*/
int main()
{
// freopen("D:\\test.txt", "r", stdin);
int n,d;
cin >> n;
int arr[n+10];
for(int i=0 ; i<n ; i++)
cin >> arr[i];
sort(arr, arr+n);
d = arr[1] - arr[0];
if(d == 0)
cout << n;
else{
for(int i=2 ; i<n ; i++)
d = __gcd(d, arr[i] - arr[i-1]);
cout << (arr[n-1]- arr[0])/d + 1;
}
return 0;
}
P1226 【模板】快速幂
题目很简单:给你三个整数 a , b , c a, b, c a,b,c,求 a b m o d p a^b\ mod\ \ p ab mod p
思路:快速幂,对每一次的结果取模
#include<iostream>
using namespace std;
typedef long long ll; // 开long long不然会爆
ll qPower(ll a, ll b, ll p)
{
ll ans;
if(b == 0) ans=1;
else{
ans = qPower(a*a%p, b/2, p); // 这里a*a必须取模
if(b%2) ans *= a;
ans %= p; // 答案取模
}
return ans;
}
int main()
{
// freopen("D:\\test.txt", "r", stdin);
ll a, b ,p;
scanf("%lld%lld%lld", &a, &b, &p);
printf("%lld^%lld mod %lld=%lld", a,b,p,qPower(a,b,p));
return 0;
}
P2249 【深基13.例1】查找
题目大意:在一个单调递增的数列中找到一个数第一次出现的位置
关键: 找到一个数的最小位置
思路: 如果这个数存在,二分查找到这个数的位置并记录,再在这个数的前面尝试能不能再找到这个数,如果能,更新它的位置。
代码实现:
#include<iostream>
#include<algorithm>
using namespace std;
int arr[1000011];
int lb(int a[], int n, int M)
{
int ans = -1; // 如果这个数不存在,ans不会被更新
int left = 1, right = n;
while(left <= right){
int mid = left+(right-left)/2;
if(a[mid] > M)
right = mid-1;
else if(a[mid] < M)
left = mid+1;
else{
ans = mid;
right = mid-1;
}
}
return ans;
}
int main()
{
// freopen("D:\\test.txt", "r", stdin);
int n,m,M;
scanf("%d%d", &n, &m);
for(int i=1 ; i<=n ; i++)
scanf("%d", &arr[i]);
for(int i=0 ; i<m ; i++){
scanf("%d", &M);
int ans = lb(arr,n,M);
printf("%d ", ans);
}
return 0;
}
P1824 进击的奶牛
题目大意:在 N N N 个位置放 C C C 个元素,找到放置每个元素的最大最短距离 a n s ans ans
思路:二分查找到这个最大最短距离
关键:找到了一个距离的时候,如何判断它是否是最大最短距离?
设这
N
N
N 个位置中第
k
k
k 个位置为
P
k
P_k
Pk,定义一个函数 isAns
,以我们得到的这个距离为最短距离从
P
1
P_1
P1 开始放置元素,看能不能放完。如果能,记录下这个距离并在更大的范围内尝试找到更大的最短距离;如果不能,在更小的范围内继续搜索。
bool isAns(int a[], int N, int C, int d) // 在有N个元素的数组a中以距离d放置C个元素
{
--C; // 第一个位置肯定要放一个元素
int i=0, j=1;
while(j<N){
if(a[j]-a[i] < d) // 说明在位置i放了一个元素后,在位置j放不了
j++;
else{
C--;
i=j;
j++;
} // 在位置j放了一个元素
}
if(C>0) return false;
else return true;
}
main
函数里只要以
P
N
−
P
1
P_N-P_1
PN−P1为最大距离,在
d
ϵ
[
1
,
P
N
−
P
1
]
d\ \epsilon\ [1,P_N-P_1]
d ϵ [1,PN−P1] 的范围内二分查找即可
完整代码如下:
时间复杂度 O ( N ∗ l o g N ) O(N*logN) O(N∗logN)
#include<iostream>
#include<algorithm>
using namespace std;
bool isAns(int a[], int N, int C, int d) // 在有N个元素的数组a中以距离d放置C个元素
{
--C; // 第一个位置肯定要放一个元素
int i=0, j=1;
while(j<N){
if(a[j]-a[i] < d) // 说明在位置i放了一个元素后,在位置j放不了
j++;
else{
C--;
i=j;
j++;
} // 在位置j放了一个元素
}
if(C>0) return false;
else return true;
}
int arr[100011];
int main()
{
// freopen("D:\\test.txt", "r", stdin);
int N, C, left, right, mid, ans;
cin >> N >> C;
for(int i=0 ; i<N ; i++)
cin >> arr[i];
sort(arr, arr+N);
left = 1, right = arr[N-1]-arr[0];
while(left<=right){
mid = (left + right)/2;
if(isAns(arr,N,C,mid)){
ans = mid;
left = mid+1;
}
else right = mid-1;
}
cout << ans;
return 0;
}
选做题
P2118 [NOIP2014 普及组] 比例简化
因为数据很小,暴力搜索即可
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
int A, B, L, Ap, Bp;
cin >> A >> B >> L;
Ap=L ; Bp=1;
for(int a=L ; a>0 ; a--)
for(int b=1 ; b<=L ; b++)
if(__gcd(a,b)==1 && a*B-A*b>=0 && Ap*b-a*Bp>=0){
Ap=a ; Bp=b;
}
cout << Ap << " " << Bp;
return 0;
}
P1024 [NOIP2001 提高组] 一元三次方程求解
因为数据很小,精确到小数点后两位,每次加0.01,暴力求解
#include<iostream>
#include<iomanip>
#include<cmath>
using namespace std;
#define eps 1e-6
double a, b, c, d;
double fun(double r)
{
return a*r*r*r + b*r*r + c*r + d;
}
int main()
{
cin >> a >> b >> c >> d;
for(double i=-100 ; i<=100 ; )
{
if(fabs(fun(i)) < eps)
{
cout << fixed << setprecision(2) << i << " ";
i += 1;
}
else
i+=0.01;
}
return 0;
}
P1873 [COCI2011-2012#5] EKO / 砍树
与必做第三第四题神似,不妨二分查找
时间复杂度 O ( N ∗ l o g N ) O(N*logN) O(N∗logN)
代码如下(勉强没超时):
#include<iostream>
using namespace std;
int arr[1000011];
int main()
{
// freopen("D:\\test.txt", "r", stdin);
int N,M;
cin>>N>>M;
for(int i=0 ; i<N ; i++)
cin>>arr[i];
int lef=1, rig=0x3fffffff, ans;
while(lef<=rig){
long long len=0; // 要开long long不然len会爆
int mid=lef+(rig-lef)/2;
for(int i=0 ; i<N ; i++)
if(arr[i]>mid)
len += arr[i]-mid;
if(len<M)
rig = mid-1;
else{
ans = mid;
lef = mid+1;
}
}
cout << ans;
return 0;
}
P3382 【模板】三分
题目大意:找一个类抛物线函数的最值点
分析:
- 首先,要求一个N次函数的值,那么我们首先要写一个函数。
- 其次,要找到这个根,可以求导后二分查找,但是导数不好求。可以直接用三分查找。
代码如下:
#include<iostream>
using namespace std;
#define eps 1e-6
double coef[20]; // 系数数组
int N;
double func(double r) // 函数f(x)
{
double res=0;
for(int i=0 ; i<=N ; i++){
double val=1;
for(int j=N-i ; j>0 ; j--)
val *= r;
val *= coef[i];
res += val;
}
return res;
}
double ts(double lef, double rig) // Ternary Search
{
double l = lef*2/3 + rig/3;
double r = lef/3 + rig*2/3;
if(rig-lef<eps) return lef;
else if(func(l)>func(r)) return ts(lef, r-eps);
else if(func(l)<func(r)) return ts(l+eps, rig);
}
int main()
{
// freopen("D:\\test.txt", "r", stdin);
double lef, rig;
cin >> N >> lef >> rig;
for(int i=0 ; i<=N ; i++)
cin >> coef[i];
cout << ts(lef, rig);
return 0;
}
P2678 [NOIP2015 提高组] 跳石头
题目大意:在N个石头中移去至多M个,使剩下来的每对相邻的石头之间的距离最大
分析:和必做第四题很像,先二分查找到一个解,然后把每个点都扫一遍判断解是否合法。如果合法,记录并在更大的范围内尝试寻找更大的解;如果不合法,在更小的范围内搜索。
判断函数:
int arr[50005],L,N,M;
bool islegal(int d)
{
int i=0, j=1, m=M; // i为当前位置,j为目标位置
while(j<=N+1){
if(arr[j]-arr[i] < d) --m;
// 如果i和j之间的距离比d还小,必须搬走j
else i=j;
++j;
}
if(m<0) return false;
else return true;
}
问题:因为我们每次搬走的都是 j j j,要遍历所有石头的话 j j j 必须从 1 1 1 扫到 N + 1 N+1 N+1 。但是由题意第 0 0 0 个石头和第 N + 1 N+1 N+1 个石头是不能搬走的,如果是第 N ( 如果它没被搬走的话 ) N(如果它没被搬走的话) N(如果它没被搬走的话) 个石头和第 N + 1 N+1 N+1 个石头之间的距离不合法,要搬走第 N + 1 N+1 N+1 个石头吗?
事实上可以这么理解:当 j = N + 1 j=N+1 j=N+1 时,假设 i = N i=N i=N ,那么 i i i 前所有的石头间距都是合法的,如果第 N N N 个石头和第 N + 1 N+1 N+1 个石头之间的距离不合法,我们可以理解成搬走了第 N N N 个石头,这样在 i i i 前面的那个石头,假设是 N − 1 N-1 N−1 ,和第 N + 1 N+1 N+1 个石头的距离一定合法。故此算法从逻辑上来说是合理的。
完整代码如下:
#include<iostream>
using namespace std;
int arr[50005],L,N,M;
bool islegal(int d)
{
int i=0, j=1, m=M; // i为当前位置,j为目标位置
while(j<=N+1){
if(arr[j]-arr[i] < d) --m; // 如果存在比d更小的间距,跳过它
else i=j;
++j;
}
if(m<0) return false;
else return true;
}
int main()
{
// freopen("D:\\test.txt", "r", stdin);
cin >>L>>N>>M;
for(int i=1 ; i<=N ; i++)
cin >> arr[i];
arr[N+1] = L;
int lef=1, rig=L, ans, mid;
while(lef<=rig){
mid = lef + (rig-lef)/2;
if(islegal(mid)){
ans = mid;
lef = mid+1;
}
else rig = mid-1;
}
cout << ans;
return 0;
}