查找满足A[i]+A[j]=j-i的数对(i,j)数量
问题描述
给定\( N \)个数字组成的数列\( {A_N} \),\( A_i \)下标从1开始,求出有多少对\( (i,j) \) 使 \( A_i+A_j = j-i \)成立。
例如对于数列 \( \{2, 3, 3, 1, 3, 1\} \),有三个数对满足条件:
\[A_1+A_4=3=4-1 \\
A_2+A_6=4 = 6-2 \\
A_4+A_6=2=6-4 \]
所以此时结果为3.
问题分析
用枚举当然很简单,可是只有小数据量时能通过。由于此题给出的数列长度达\( 2\times 10^5 \),\(O(N^2) \)的枚举法会超时。
Haskell
x
5
1
main = do
2
line<-getLine
3
line<-getLine
4
let p = zip [1..] [read x::Int|x<-words line]
5
print $ length [1|x<-p,y<-p,fst y > fst x,(fst y)-(fst x)==(snd x)+(snd y)]
要想办法优化到最多 \( O(N \log(N) )\)的时间,也就是想办法将逐元素的顺序查找改为二分查找。
我们要对数列\( A_N \)中的两个元素\( A_i,A_j\)符合\[ A_i+A_j = j-i \]
移项即\[ i+A_i = j-A_j \]
令\( L_i = i+A_i \), \(R_i = i-A_i \),我们只需要对每个\( L \)中的元素\( L_i \),查找\( R \)中有多少个与 \( L_i \) 相等的即可。估算一下复杂度,对R进行1次排序并进行N次查找, 理论上可以在 \( O(N \log(N) )\)的时间完成。
此题\( A_i \)达\( 10^9 \),且\( N \) 最大\( 2\times 10^5\),在计算\( L_i = i+A_i \) 时可能造成int
范围溢出,所以将数组设为long long
类型
参考程序
C++
1
28
28
1
2
3
using std::sort;
4
using std::lower_bound;
5
using std::upper_bound;
6
const int MAXN=200003;
7
long long a[MAXN],L[MAXN],R[MAXN];
8
int main(void)
9
{
10
int i,N;
11
scanf("%d",&N);
12
for(i=0;i<N;i++)
13
{
14
scanf("%lld",a+i);
15
L[i] = i+1+a[i];
16
R[i] = (i+1)-a[i];
17
}
18
sort(R,R+N);
19
long long ret = 0,lwbd,upbd;
20
for(i=0;i<N;i++)
21
{
22
lwbd = lower_bound(R,R+N,L[i]) - &R[0];
23
upbd = upper_bound(R,R+N,L[i]) - &R[0];
24
ret+=upbd-lwbd;
25
}
26
printf("%lld\n",ret);
27
return 0;
28
}
用Python写时间比较极限,提交了一次运行1996ms,时限就2000ms
Python
1
32
32
1
def lower_bound(arr,tar):
2
def _lower_bound(first,length):
3
if(length==0):
4
return first
5
half = length//2
6
middle = first+half
7
if arr[middle]<tar:
8
return _lower_bound(middle+1,length-half-1)
9
else:
10
return _lower_bound(first,half)
11
return _lower_bound(0,len(arr))
12
def upper_bound(arr,tar):
13
def _upper_bound(first,length):
14
if(length==0):
15
return first
16
half = length//2
17
middle = first+half
18
if arr[middle]>tar:
19
return _upper_bound(first,half)
20
else:
21
return _upper_bound(middle+1,length-half-1)
22
return _upper_bound(0,len(arr))
23
N=int(input())
24
a=list(map(int,input().split()))
25
L = [(i+1)+a[i] for i in range(N)]
26
R = [(i+1)-a[i] for i in range(N)]
27
R.sort()
28
s=0
29
for it in L:
30
s+=upper_bound(R,it)-lower_bound(R,it)
31
print(s)
32
Haskell写了一样的方法但是只有小数据能过,还需要再进一步学习
Haskell
1
29
29
1
import Data.List
2
main = do
3
line<-getLine
4
line<-getLine
5
let a = [read x::Int|x<-words line]
6
let aL = [x+y|(x,y)<-zip a [1..]]
7
let aR = sort [y-x|(x,y)<-zip a [1..]]
8
print $ sum $ map (\x ->(upper_bound aR x) - ( lower_bound aR x)) aL
9
lower_bound :: [Int]->Int->Int
10
lower_bound a t = _lower_bound a 0 (length a) t
11
_lower_bound :: [Int]->Int->Int->Int->Int
12
_lower_bound a first 0 _= first
13
_lower_bound a first len target = do
14
let half = div len 2
15
let middle = first + half
16
if a!!middle < target
17
then _lower_bound a (middle+1) (len-half-1) target
18
else _lower_bound a first half target
19
upper_bound :: [Int]->Int->Int
20
upper_bound a t = _upper_bound a 0 (length a) t
21
_upper_bound :: [Int]->Int->Int->Int->Int
22
_upper_bound a first 0 _ = first
23
_upper_bound a first len target = do
24
let half = div len 2
25
let middle = first + half
26
if a!!middle > target
27
then _upper_bound a first half target
28
else _upper_bound a (middle+1) (len-half-1) target
29