分治算法

今天来进行分治算法的学习~

问题引入:

题目描述:
请在一个有序数组中,找出值x的位置,如果x不在数组中,输出-1。
输入:
第一行,一个整数n,代表数组元素个数第二行,n个数,代表数组的n个元素(n<=1000000)第三行,一个整数x,代表要查找的数
输出:
x在数组中的位置

由于数组过大,使用循环进行遍历并不高效,于是我们考虑如下思想:

对于一个按照升序排列的数组,优先规定其左边界为left,右边界为right,中间mid=(left+right)/2 ,若A[mid]>x则说明mid大了,将右边界移动到mid左边一位。vice versa若A[mid]<x 则说明mid小了,左边界移动到mid右一位。而若A[mid]==x 就是找到啦!

下面进行代码实现,首先我们尝试使用while循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<iostream>
using namespace std;
int M[1000000];
int main(){
int n,post=-1;
cin>>n;
for(int i=0;i<n;i++){
cin>>M[i];
}
int x;
cin>>x;
int left=0,right=n-1;
while(left<=right){
int mid=(left+right)/2;
if(M[mid]==x){
post=mid+1;
break;
}
else if(M[mid]>x){
right=mid-1;
}
else if(M[mid]<x){
left=mid+1;
}
}
cout<<post;



return 0;
}

同样的,我们意识到递归也能解决同样的问题,下面我们试图使用递归来解决:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include<iostream>
using namespace std;
int M[1000000];
int x;
int search(int left,int right){
int mid=(left+right)/2;
if(left>right){
return -1;
}
if(M[mid]==x){
return mid+1;
}
if(M[mid]>x){
return search(left,mid-1);
}
if(M[mid]<x){
return search(mid+1,right);
}
}
int main(){
int n;
cin>>n;
for(int i=0;i<n;i++){
cin>>M[i];
}
cin>>x;
cout<<search(0,n-1);



return 0;
}

在完成了基本二分思想的实现之后,我们尝试解决P2249 【深基13.例1】查找 - 洛谷

P2249 【深基13.例1】查找

题目描述

输入 $n$ 个不超过 $10^9$ 的单调不减的(就是后面的数字不小于前面的数字)非负整数 $a1,a_2,\dots,a{n}$,然后进行 $m$ 次询问。对于每次询问,给出一个整数 $q$,要求输出这个数字在序列中第一次出现的编号,如果没有找到的话输出 $-1$ 。

输入格式

第一行 $2$ 个整数 $n$ 和 $m$,表示数字个数和询问次数。

第二行 $n$ 个整数,表示这些待查询的数字。

第三行 $m$ 个整数,表示询问这些数字的编号,从 $1$ 开始编号。

输出格式

输出一行,$m$ 个整数,以空格隔开,表示答案。

输入输出样例 #1

输入 #1

1
2
3
11 3
1 3 3 3 5 7 9 11 13 15 15
1 3 6

输出 #1

1
1 2 -1

说明/提示

数据保证,$1 \leq n \leq 10^6$,$0 \leq a_i,q \leq 10^9$,$1 \leq m \leq 10^5$

本题输入输出量较大,请使用较快的 IO 方式。

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include<iostream>
using namespace std;
int n,m;
int M[1100000],N[110000];
int search(int left,int right,int i){
int mid=(left+right)/2;
if(left>right){
return -1;
}
if(M[mid]==N[i]){
if(mid!=0){
if(M[mid-1]==N[i]) return search(left-1,right-1,i);
return mid+1;
}
else return mid+1;

}
else if(M[mid]<N[i]){
return search(mid+1,right,i);
}
else if(M[mid]>N[i]){
return search(left,mid-1,i);
}
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
cin>>n>>m;
for(int i=0;i<n;i++){
cin>>M[i];
}
for(int i=0;i<m;i++){
cin>>N[i];
}
for(int i=0;i<m;i++){
cout<<search(0,n-1,i);
if(i!=m) cout<<" ";
}


return 0;
}

可以看到,即便关闭了iostream的同步,最后一个节点还是会TLE,于是我们需要试图继续提高io的速度,如果io速度提高也不行,我们需要重构算法

改为使用printf和scanf,仍然tle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include<cstdio>
int n,m;
int M[1100000],N[110000];
int search(int left,int right,int i){
int mid=(left+right)/2;
if(left>right){
return -1;
}
if(M[mid]==N[i]){
if(mid!=0){
if(M[mid-1]==N[i]) return search(left-1,right-1,i);
return mid+1;
}
else return mid+1;

}
else if(M[mid]<N[i]){
return search(mid+1,right,i);
}
else if(M[mid]>N[i]){
return search(left,mid-1,i);
}
}
int main(){
scanf("%d %d", &n,&m);
for(int i=0;i<n;i++){
scanf("%d",&M[i]);
}
for(int i=0;i<m;i++){
scanf("%d",&N[i]);
}
for(int i=0;i<m;i++){
printf("%d",search(0,n-1,i));

if(i!=m-1) printf(" ");
}


return 0;
}

那么只剩下重构算法了,我们不再尝试利用递归,改为重新用loop,刚刚使用递归只是为了熟悉下递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include<cstdio>
int n,m;
int M[1100000],N[110000];

int search(int left,int right,int i){
int post=-1;
while(left<=right){
int mid=(left+right)/2;
if(M[mid]==N[i]){
post=mid+1;
right=mid-1;
}
else if(M[mid]>N[i]){
right=mid-1;
}
else if(M[mid]<N[i]){
left=mid+1;
}
}
return post;

}
int main(){
scanf("%d %d", &n,&m);
for(int i=0;i<n;i++){
scanf("%d",&M[i]);
}
for(int i=0;i<m;i++){
scanf("%d",&N[i]);
}
for(int i=0;i<m;i++){
printf("%d",search(0,n-1,i));

if(i!=m-1) printf(" ");
}


return 0;
}

成功AC