原题
题目描述 Description
给出n个数,统计两两之和小于k的方案数之和。
输入描述 Input Description
第一行一个数n,表示数字的个数;
第二行到第n + 1行,每行一个不超过2,000,000,000的数k;
第n + 2行一个数m,表示m个问题;
第n + 3行到第n + m + 2行,每行一个数m,询问表示n中两两组合不超过m的组
合的个数;
输出描述 Output Description
输出m行,每行对应一个答案
样例输入 Sample Input
3
1
2
3
2
2
3
样例输出 Sample Output
0
1
数据范围及提示 Data Size & Hint
30%的数据1 ≤ n ≤ 100, 1 ≤ m ≤ 50, k ≤ 2000;
100%的数据 1 ≤ n ≤ 10000, 1 ≤ m ≤ 100, k ≤ 2,000,000,000
题解
刚开始看到前面以为是道水题,想打个打暴力水过,看到后面数据怂了,只好再想优化方法。。。
后来还是想到了二分法,但输入时是无序的,要先敲一个快排从小到大排,接着二分是关键。
设一个函数find(l,r,x),表示区间[l,r]中,不超过x的最大值的位置。
接着在每次读入k的时候,求出区间[1,n]中不超过k的最大值的位置p【这步其实不要也可以,只是效率更高】,接着用一个j循环1~p,每次求出find(j,p+1,k-a[j])-j【也就是能与a[j]结成对子的方案数】,再更新ans,输出。
1 var a:array[1..10000] of int64; 2 var n,m,k,p,ans:int64; 3 var i,j:longint; 4 function find(l,r,x:int64):int64;//二分 5 var mid:int64; 6 begin 7 while l+1<r do 8 begin 9 mid:=(l+r) div 2; 10 if a[mid]<=x then l:=mid else r:=mid; 11 end; 12 exit(l); 13 end; 14 procedure q(l,r:longint); 15 var i,j,m,t:longint; 16 begin 17 i:=l;j:=r; 18 m:=a[(l+r) div 2]; 19 repeat 20 while a[i]<m do inc(i); 21 while a[j]>m do dec(j); 22 if i<=j then 23 begin 24 t:=a[i];a[i]:=a[j];a[j]:=t; 25 inc(i);dec(j); 26 end; 27 until i>j; 28 if i<r then q(i,r); 29 if l<j then q(l,j); 30 end; 31 begin 32 readln(n); 33 for i:=1 to n do read(a[i]);q(1,n);//读入&排序 34 readln(m); 35 for i:=1 to m do 36 begin 37 ans:=0;readln(k); 38 p:=find(1,n+1,k);//找出最小的枚举区间 39 for j:=1 to p do ans:=ans+find(j,p+1,k-a[j])-j;//关键代码,好好理解 40 writeln(ans); 41 end; 42 end.
欢迎转载,请注明出处。
时间: 2024-10-06 13:49:56