Description
You have N sticks on your hands. When I show you a stick, you should tell me how many pairs of sticks on your hands can make a triangle with my stick.
Input
The first line is a positive integer T standing for the numbers of test cases. The first line of each test case is two positive integers N, M, standing for the number of sticks on your hands and the times I will show you a stick. The second line has N positive integers. Each stands for i-th length of a stick on your hands. The i-th stick's length is li. Then M lines follow .Each is a positive integer Ki indicating the length of stick that I show you.(1<=T<=100,2<=N<=2000,1<=M<=100000,1<=li<=50000,1<=ki<=100000)
Output
For each stick I show you, output the number of pair that can make a triangle with this stick. Each output should be in one line.
Sample Input
2 3 2 2 4 6 5 16 3 1 12 12 18 12
Sample Output
3 0 3
题目大意:你手上已经有了n根棒子,长度分别为l1,l2,....,ln,现在每次给你一根长为ki的棒子(总共给m次),从你已有的棒子中取出两根棒子,问有多少种取法使取出的棒子与给你的棒子能构成三角形?
题目意思很好懂,如果用暴搜的话,思路也很简单,对每个ki,用两层循环从l1~ln中依次枚举两根棒子,对li,lj,ki排序,判断条件l1+l2>l3(li,lj,ki排序后,按长短依次为l1,l2,l3)是否成立,但是这样的程序显然超时,复杂度为O(m*n^2),比如下面就是一个TLE的程序:
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<ctime> 4 int n[2001]; 5 int m[100001]; 6 int x[3]; 7 8 int cmp(const void *elem1,const void *elem2) 9 { 10 return *(int *)elem1-*(int *)elem2; 11 } 12 13 14 int main() 15 { 16 // freopen("in.txt","r",stdin); 17 // freopen("out.txt","w",stdout); 18 int T; 19 scanf("%d",&T); 20 while(T--) 21 { 22 int N,M; 23 scanf("%d %d",&N,&M); 24 for(int i=1; i<=N; ++i) 25 { 26 scanf("%d",&n[i]); 27 } 28 for(int i=1; i<=M; ++i) 29 { 30 scanf("%d",&m[i]); 31 } 32 33 int flag; 34 for(int i=1; i<=M; ++i) 35 { 36 flag=0; 37 int cnt=0; 38 for(int j=1; j<=N-1; ++j) 39 { 40 for(int k=j+1; k<=N; ++k) 41 { 42 x[0]=n[j],x[1]=n[k],x[2]=m[i]; 43 qsort(x,3,sizeof(x[0]),cmp); 44 if(x[0]+x[1]>x[2]) 45 cnt++; 46 } 47 } 48 printf("%d ",cnt); 49 // printf("%lf ",(double)clock()/CLOCKS_PER_SEC); 50 } 51 } 52 53 return 0; 54 }
而我的思路是根据判断式l1+l2>l3,即l2>l3-l1,用一层循环遍历l1~ln,然后把第二层循环改为二分搜索。本来我准备自己写一个二分搜索的函数,但写到一半,突然想起来 watashi 以前提到过的库函数:upper_bound 和 lower_bound 。
其中 upper_bound 的用法是:iterator = lower_bound(nums.begin(),nums.end(),key_val); 前两个参数分别是容器的首尾迭代器,第三个参数是待查找的键值,函数返回的是一个迭代器,即指向键值>= key的第一个元素。而 upper_bound是返回一个迭代器,指向键值> key的第一个元素。所以 upper_bound- lower_bound就是键值在容器中的出现次数,这一点经常会用到。
下面是思路的具体描述:首先对 l1~ln排序,得到递增序列 l1'~ln'。然后对于每一个ki,可以分成两种情况:1.ki是最大边;2.ki是其中一条小边。利用 lower_bound函数将ki插入到序列中,返回key1,即 ki介于l1'~lkey1和lkey1+1~ln之间。
对于情况一,就是从 l1'~lkey1中找两条边,此时有 li+lj>ki,即 lj>ki-li,从 l1'遍历到 lkey1,对于每一个 li,将 ki-li插入到 l1'~lkey1中,得到key2,则 lkey2~lkey1都是满足的,key1-key2即为情况一的总数。
对于情况二,就是从 lkey1+1~ln'中找到一条大边,从 l1'~ln'中找到一条小边(其中小边下标小于大边)。此时有 l小+ki>l大,即 l小>l大-ki。同理,从 lkey1+1~ln',对于每一个 l大,将 l大-ki 插入到 l1'~ln' 中,得到key3,则满足的个数为:大边下标-key3-1。
两种情况相加即为最终结果。代码如下:
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 5 //int bin_ser(int* a,int n,int key){ 6 // int left=0,right=n-1,middle=(left+right)/2; 7 // while() 8 // if(key<=a[middle]){ 9 // right=middle; 10 // middle=(left+right)/2; 11 // } 12 // else{ 13 // left=middle; 14 // middle=(left+right)/2; 15 // } 16 // 17 //} 18 19 int l[60000]; 20 int k[200000]; 21 22 int main(){ 23 int t,n,m,key1,key2,key3,ans1,ans2,ans; 24 scanf("%d",&t); 25 26 while(t--){ 27 scanf("%d%d",&n,&m); 28 for(int i=0;i<n;++i) 29 scanf("%d",&l[i]); 30 for(int j=0;j<m;++j) 31 scanf("%d",&k[j]); 32 33 sort(l,l+n); 34 35 // for(int i=0;i<n-1;++i) 36 // s[i]=l[i]+l[i+1]; 37 38 for(int i=0;i<m;++i){ 39 key1=lower_bound(l,l+n,k[i])-l-1; 40 // key2=lower_bound(s,s+n-1,k[j])-1; 41 42 ans1=0; 43 for(int j=0;j<=key1-1;++j){ 44 key2=upper_bound(l,l+n,k[i]-l[j])-l-1; 45 ans1+=key1-key2; 46 } 47 48 ans2=0; 49 for(int j=key1+1;j<n;++j){ 50 key3=upper_bound(l,l+n,l[j]-k[i])-l-1; 51 ans2+=j-key3-1; 52 } 53 54 55 ans=ans1+ans2; 56 printf("%d ",ans); 57 58 } 59 60 } 61 return 0; 62 }
复杂度分析:对 l1~ln排序为O(n*logn),后面的过程为O(m*(logn+k*logn+(n-k)*logn))=O(m*n*logn),总复杂度为O(n*logn+m*n*logn)=O(m*n*logn)。
即此程序把开头的那个程序的复杂度从O(m*n^2)优化到了O(m*n*logn)。