我正在尝试解决类似这样的问题:
我给了n个数字(1 <= n <= 10 ^ 5)。我必须写出左边所有数字的总和,这些数字小于当前数字并重复所有n个数字的过程。然后我必须找到所有先前获得的和的总和。(每个数字N,0 <= N <= 10 ^ 6)。
例如,
1 5 3 6 4
less1 less5 less3 less6 less4
(0) + (1) + (1)+(1+5+3)+(1+3)
0 + 1 + 1 + 9 + 4
= 15
这个问题的一个简单的解决方案是运行两个循环,并且对于每个给定的数字,找到小于该数字的所有数字的总和,最后给出这些和的总和作为输出。时间复杂度为O(n ^ 2)。
我认为使用二进制索引树(Fenwick树)可以更好地解决这个问题的O(nlogn)解决方案。 对于每个数字,我将在全局数组a中添加每个数字并执行两个明显的BIT操作。我认为该算法的时间复杂度为O(nlogn),如果为真,则明显优于之前的O(n ^ n ^ 2)。
我已用C ++实现了代码。
#include<iostream>
#include<cstdio>
using namespace std;
#define max 1000001
long int a[max];
void add(long int v,int idx){
while(idx<max){
a[idx] += v;
idx += (idx & -idx);
}
}
long int sum(int idx){
long int s=0;
while(idx>0){
s += a[idx];
idx -= (idx & -idx);
}
return s;
}
int main()
{
int t;
scanf("%d",&t);
for(int w=0;w<t;w++){
int n;
scanf("%d",&n);
for(int i=0;i<max;i++)
a[i]=0;
int arr[n];
for(int i=0;i<n;i++)
scanf("%d",&arr[i]);
long long res=0;
for(int i=0;i<n;i++){
if(arr[i]!=0){
add(arr[i],arr[i]);
res += (sum(arr[i]-1));
}
}
printf("%lld\n",res);
}
return 0;
}
我有两个问题:
首先,我做得对吗? /我的逻辑是否正确?
第二,如果我把时间复杂度定为O(nlogn)那么为什么它运行缓慢?你能帮助我进一步优化吗?
已接受1.41秒。同时我更新了我最终接受的代码。有关优化的建议吗?
基于这些评论,我尝试了自己的功能,以实现更快的I / O,但仍然没有按照我的方式进行。这是我的快速I / O功能:
inline int read(){
char c=getchar_unlocked();
int n=0;
while(!(c>='0' && c<='9'))
c=getchar_unlocked();
while(c>='0' && c<='9'){
n=n*10 + (c-'0');
c=getchar_unlocked();
}
return n;
}
这是问题的链接:
http://www.spoj.pl/problems/DCEPC206/
如果有人能够解决它,请告诉我。 感谢。
答案 0 :(得分:2)
这是另一种方法:问题类似于计算反转,除非您必须对负责生成反转的元素求和。我们可以使用merge sort来解决这个问题。像这样修改合并函数:
merge(left, middle, right, array)
temp = new array
k = 0, i = left, j = middle + 1
while i <= middle and j <= right
if array[i] < array[j]
temp[k++] = array[i]
// array[i] is also smaller than all array[j+1], ..., array[right]
globalSum += array[i] * (right - j + 1)
else
// same as the classical function
直观地说,我会说递归mergesort比BIT解决方案慢,但谁知道呢?试一试。
编辑:这是AC:
#include<stdio.h>
#include <iostream>
using namespace std;
#define max 100001
int n;
long long res = 0;
int temp[max];
int arr[max];
void merge(int left, int m, int right)
{
int k = 0;
int i = left, j = m + 1;
while (i <= m && j <= right)
if (arr[i] < arr[j])
{
temp[k++] = arr[i];
res += (long long)(right - j + 1) * arr[i++];
}
else
temp[k++] = arr[j++];
while (j <= right)
temp[k++] = arr[j++];
while (i <= m)
temp[k++] = arr[i++];
for (int i = 0; i < k; ++i)
arr[left + i] = temp[i];
}
void sort(int left, int right)
{
if (left < right)
{
int m = left + (right - left) / 2;
sort(left, m);
sort(m + 1, right);
merge(left, m, right);
}
}
int main()
{
int t;
scanf("%d", &t);
for(int w=0;w<t;w++)
{
scanf("%d", &n);
for(int i=0;i<n;i++)
scanf("%d", &arr[i]);
res=0;
sort(0, n - 1);
printf("%lld\n",res);
}
return 0;
}
答案 1 :(得分:2)
我认为你的方法很好。我玩过这个小小的玩具并没有提出任何比你拥有的更好的东西。
但是代码中有一些错误。有几个地方遭遇整数溢出。你应该改为:
long long a[max];
和
long long sum(int idx){
long long s=0;
更明显的错误是您汇总的数字小于或等于当前数字。要解决此问题,您可以添加第二个全局数组来跟踪每个值的计数:
int b[max];
...
...
for(int i=0;i<max;i++)
a[i]=b[i]=0;
...
...
res += (sum(idx)-(++b[idx]*val));
可能有一种更有效的方法来修复该错误,但总体而言,它似乎仍然是一种快速解决方案。