• NENU ACM 2014-05-18 G题 && NENU 1087 Magic Triangle


    G - Magic Triangle
    Time Limit:1000MS     Memory Limit:131072KB     64bit IO Format:%lld & %llu

    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)。

  • 相关阅读:
    C#中 栈,堆你真的懂吗?不理解引用类型和值类型区别的程序员将会给代码引入诡异的bug和性能问题
    c# 可空类型,语法糖,lambda,命名规则(Pascal 帕斯卡命名,Camel 驼峰命名),注释,封装,继承,多态
    数据库事务,游标,触发器,存储过程,索引,数字,日期转换为字符,字符串操作,查询,分类,内连接,外连接,全连接,模糊查询,范围查询,5种聚合函数,分组查询,主键,外键,标识列
    html,css 知识汇总
    html,css,js,jquery
    数据库文件托管
    final,finally,finalize的区别
    Thread,Threadpool,task的区别
    ABP 一个开源的web开发框架
    redis 40问
  • 原文地址:https://www.cnblogs.com/wkxnk/p/3738707.html
Copyright © 2020-2023  润新知