计数在有效算法中选择两个数的方法

Count number of ways for choosing two numbers in efficient algorithm

本文关键字:两个 方法 选择 有效 算法      更新时间:2023-10-16

我解决了这个问题,但是我在在线裁判上得到了时间限制超过

程序的输出是正确的,但我认为这种方式可以改进,以提高效率!

问题:

给定n整数,计算我们可以选择两个元素如下的方法的个数它们的绝对差小于32

用更正式的方式,计算(i, j) (1 ≤ i < j ≤ n)对的个数,使

|V[i] - V[j]| < 32|X|X的绝对值。

输入

第一行输入包含一个整数T,测试用例的数量(1 ≤ T ≤ 128)

每个测试用例以一个整数n (1 ≤ n ≤ 10,000)开始。

下一行包含n整数(1 ≤ V[i] ≤ 10,000)

对于每个测试用例,在一行上打印成对的数目。

我的c++代码:

int main() {
    int T,n,i,j,k,count;
    int a[10000];
    cin>>T;
for(k=0;k<T;k++)
 {   count=0;
     cin>>n;
    for(i=0;i<n;i++)
    {
      cin>>a[i];
    }
    for(i=0;i<n;i++)
    {
        for(j=i;j<n;j++)
        {
          if(i!=j)
          {
            if(abs(a[i]-a[j])<32)
                count++;
          }
        }
    }
    cout<<count<<endl;
 }
    return 0;
}

我需要帮助,我如何才能解决它在更有效的算法

尽管我之前(愚蠢)的答案,没有必要对数据进行排序。相反,你应该计算数字的频率。

那么你所需要做的就是跟踪要配对的可行数字的数量,同时迭代可能的值。抱歉没有c++,但是java应该也是可读的:

int solve (int[] numbers) {                                                 
    int[] frequencies = new int[10001];                                     
    for (int i : numbers) frequencies[i]++;                                 
    int solution = 0;                                                       
    int inRange = 0;                                                        
    for (int i = 0; i < frequencies.length; i++) {                          
        if (i > 32) inRange -= frequencies[i - 32];                         
        solution += frequencies[i] * inRange;                               
        solution += frequencies[i] * (frequencies[i] - 1) / 2;              
        inRange += frequencies[i];                                           
    }                                                                        
    return solution;                                                         
}    
#include <bits/stdc++.h> 
using namespace std;
int a[10010];
int N;
int search (int x){
int low = 0;
int high = N;
while (low < high)
{
    int mid = (low+high)/2;
    if (a[mid] >= x) high = mid;
    else low = mid+1;
}
return low;
}
int main() {
    cin >> N;
    for (int i=0 ; i<N ; i++) cin >> a[i];
    sort(a,a+N);
    long long ans = 0;
    for (int i=0 ; i<N ; i++)
    {
        int t = search(a[i]+32);
        ans += (t -i - 1);
    }
    cout << ans << endl;
    return 0;
}

您可以对数字进行排序,然后使用滑动窗口。从最小的数字开始,用不大于最小的数字+ 31的数字填充std::deque。然后在每个数字的外部循环中,更新滑动窗口并将滑动窗口的新大小添加到计数器中。滑动窗口的更新可以在内部循环中执行,首先pop_front每个小于外部循环当前数的数字,然后push_back每个不大于外部循环当前数+ 31的数字。

一个更快的解决方案是首先对数组进行排序,然后遍历已排序的数组,对于每个元素只访问它右边的元素,直到差值超过31。

排序可以通过计数排序来完成(因为你有1≤V[i]≤10,000)。所以排序部分的时间是线性的。这可能不是必需的(也许快速排序足以获得所有的点)。

同样,你也可以对内循环("移到当前元素的右边"部分)做一个技巧。记住,如果S[i+k]-S[i]<32,那么S[i+k]-S[i+1]<32,其中S是v的排序版本,有了这个技巧,整个算法就变成线性了。

这可以在数据传递的恒定次数下完成,并且实际上可以在不受"interval"值影响的情况下完成(在您的示例中,为32)。这是通过填充一个数组来完成的,其中a[i] = a[i-1] + number_of_times_i_appears_in_the_data(非正式地,a[i])保存小于/等于i的元素的总数。

代码(用于单个测试用例):

static int UPPER_LIMIT = 10001;
static int K = 32;
int frequencies[UPPER_LIMIT] = {0}; // O(U)
int n;
std::cin >> n;
for (int i = 0; i < n; i++) { // O(n)
 int x;
 std::cin >> x;
 frequencies[x] += 1;
}
for (int i = 1; i < UPPER_LIMIT; i++) { // O(U)
    frequencies[i] += frequencies[i-1];
}
int count = 0;
for (int i = 1; i < UPPER_LIMIT; i++) { // O(U)
  int low_idx = std::max(i-32, 0);
  int number_of_elements_with_value_i = frequencies[i] - frequencies[i-1];
  if (number_of_elements_with_value_i == 0) continue;
  int number_of_elements_with_value_K_close_to_i =
      (frequencies[i-1] - frequencies[low_idx]);
  std::cout << "i: " << i << " number_of_elements_with_value_i: " << number_of_elements_with_value_i << " number_of_elements_with_value_K_close_to_i: " << number_of_elements_with_value_K_close_to_i << std::endl;
  count += number_of_elements_with_value_i * number_of_elements_with_value_K_close_to_i;
  // Finally, add "duplicates" of i, this is basically sum of arithmetic 
  // progression with d=1, a0=0, n=number_of_elements_with_value_i
  count += number_of_elements_with_value_i * (number_of_elements_with_value_i-1) /2;
}
std::cout << count;

在IDEone上工作的完整示例。

可以排序,然后在超出范围时使用break结束循环。

int main()
{
    int t;
    cin>>t;
    while(t--){
        int n,c=0;
        cin>>n;
        int ar[n];
        for(int i=0;i<n;i++)
            cin>>ar[i];
        sort(ar,ar+n);
        for(int i=0;i<n;i++){
            for(int j=i+1;j<n;j++){
                if(ar[j]-ar[i] < 32)
                    c++;
                else
                    break;
            }
        }
        cout<<c<<endl;
    }
}

或者,您可以使用哈希数组来表示范围并标记每个元素的出现情况,然后循环并检查每个元素,即x = 32 - y是否存在。

这里的一个好方法是将数字分成单独的桶:

constexpr int limit = 10000;
constexpr int diff = 32;
constexpr int bucket_num = (limit/diff)+1;
std::array<std::vector<int>,bucket_num> buckets;
cin>>n;
int number;
for(i=0;i<n;i++)
{
    cin >> number;
    buckets[number/diff].push_back(number%diff);
}

显然,同一桶中的数字彼此足够接近以满足要求,因此我们只需计算所有对:

int result = std::accumulate(buckets.begin(), buckets.end(), 0,
                  [](int s, vector<int>& v){ return s + (v.size()*(v.size()-1))/2; });  

在非相邻桶中的数字不能形成任何可接受的对,所以我们可以忽略它们。

这就剩下了最后一个角落的情况——相邻的桶——这可以用多种方法解决:

for(int i=0;i<bucket_num-1;i++)
   if(buckets[i].size() && buckets[i+1].size())
      result += adjacent_buckets(buckets[i], buckets[i+1]);

就我个人而言,我喜欢在一个桶的尺度上使用"发生频率"方法,但可能有更好的选择:

int adjacent_buckets(const vector<int>& bucket1, const vector<int>& bucket2)
{
    std::array<int,diff> pairs{};
    for(int number : bucket1)
     {
         for(int i=0;i<number;i++)
            pairs[i]++;
     }
    return std::accumulate(bucket2.begin(), bucket2.end(), 0,
                           [&pairs](int s, int n){ return s + pairs[n]; }); 
}

此函数首先构建一个"来自较低的桶中与i足够接近的数字"的数组,然后对该数组中与较高的桶中数字相对应的值求和。

一般来说,这种方法的复杂度为0 (N),在最好的情况下,它几乎只需要一次传递,总的来说应该足够快。

工作Ideone示例

此解决方案可以考虑为O(N)处理N个输入数字,在处理输入的时间上为常数:

#include <iostream>
using namespace std;
void solve()
{
    int a[10001] = {0}, N, n, X32 = 0, ret = 0;
    cin >> N;
    for (int i=0; i<N; ++i)
    {
        cin >> n;
        a[n]++;
    }
    for (int i=0; i<10001; ++i)
    {
        if (i >= 32)
            X32 -= a[i-32];
        if (a[i])
        {
            ret += a[i] * X32;
            ret += a[i] * (a[i]-1)/2;
            X32 += a[i];
        }
    }
    cout << ret << endl;
}
int main()
{
    int T;
    cin >> T;
    for (int i=0 ; i<T ; i++)
        solve();
}

在ideone上运行此代码

解决方案说明:a[i]表示i在输入序列中出现的次数。然后你遍历整个数组,X32会跟踪i范围内的元素数量。唯一棘手的部分是当某些i重复多次时正确计算:a[i] * (a[i]-1)/2。就是这样。

您应该从对输入进行排序开始。

如果你的内环检测到距离超过32,你就可以脱离它。

感谢大家为解决这个问题所付出的努力和时间。

我感谢所有为解决这个问题所做的努力。

在测试了在线法官的答案后,我发现正确和最有效的解决算法是Stef的答案和AbdullahAhmedAbdelmonem的答案也是pavel解决方案是正确的,但它与不同语言c++中的Stef解决方案完全相同。

Stef的代码在codeforces online judge中获得时间执行358 ms并被接受。

同时,AbdullahAhmedAbdelmonem的代码在codeforces online judge中获得了执行时间421 ms并被接受。

如果他们详细解释了他们的算法,赏金将给他们中的一个。

您可以尝试您的解决方案,并在选择问题e后将其提交给codeforces在线评委。

我也发现了一个很好的算法解决方案,更容易理解使用频率阵列和它的复杂性O(n)。

在这个算法中,你只需要为每个插入的元素取一个特定的范围,即

begin = element - 32  
end = element + 32

,然后计算频率数组中每个插入元素在这个范围内的对个数:

int main() {
    int T,n,i,j,k,b,e,count;
    int v[10000];
    int freq[10001];
    cin>>T;   
 for(k=0;k<T;k++)
 { 
    count=0;
    cin>>n;
    for(i=1;i<=10000;i++)
    {
      freq[i]=0;
    }
    for(i=0;i<n;i++)
    {
     cin>>v[i];
    }   
    for(i=0;i<n;i++)
    {
     count=count+freq[v[i]];
     b=v[i]-31;
     e=v[i]+31;
     if(b<=0)
         b=1;
     if(e>10000)
        e=10000;
     for(j=b;j<=e;j++)
      {
        freq[j]++;
      }
    }
    cout<<count<<endl;
 }
    return 0;
}

最后,我认为解决这类问题的最好方法是使用频率阵列并在特定范围内计数对数,因为它的时间复杂度为O(n)。