以下是问题(链接:http://opc.iarcs.org.in/index.php/problems/FINDPERM):
数字1,...,N的排列是这些数字的重新排列。例如
2 4 5 1 7 6 3 8
是1,2,...,8的排列。当然,
1 2 3 4 5 6 7 8
也是1,2,...,8的排列 与N的每个排列相关联的是长度为N的正整数的特殊序列,称为其反转序列。该序列的第i个元素是严格小于i的数字j的数量,并且在该排列中出现在i的右侧。对于排列
2 4 5 1 7 6 3 8
反转序列是
0 1 0 2 2 1 2 0
第二个元素是1,因为1严格小于2,在这个排列中它出现在2的右边。类似地,第5个元素是2,因为1和3严格小于5但在此排列中出现在5的右侧,依此类推。
作为另一个例子,置换的反转序列
8 7 6 5 4 3 2 1
是
0 1 2 3 4 5 6 7
在这个问题中,你将得到一些排列的反演序列。你的任务是从这个序列中重建排列。
我想出了这段代码:
#include <iostream>
using namespace std;
void insert(int key, int *array, int value , int size){
int i = 0;
for(i = 0; i < key; i++){
int j = size - i;
array[ j ] = array[ j - 1 ];
}
array[ size - i ] = value;
}
int main(){
int n;
cin >> n;
int array[ n ];
int key;
for( int i = 0; i < n; i++ ){
cin >> key;
insert( key, array, i + 1, i);
}
for(int i = 0;i < n;i ++){
cout << array[i] << " ";
}
return 0;
}
它正常工作并为70%的测试用例提供了正确的答案,但是超过了剩余的时间限制。 还有其他更快的算法来解决这个问题吗?
答案 0 :(得分:7)
您的算法具有复杂度O(N^2)
操作,因此对于大小为10^5
的数组,它需要太多时间才能执行。我试着描述更好的解决方案:
我们有N
个号码。让我们调用逆数组I
。解决这个问题,我们需要知道在置换结束时K-th
的位置仍然是免费的(让我们调用这个函数F(K)
)。首先,我们将数字N
放到F(I[N] + 1)
位置,然后将数字N-1
放到F(I[N-1] + 1)
位置,依此类推。
F
可按如下方式计算:声明大小为M
的数组N
:1 1 1 ... 1
,定义S(X) = M[1] + M[2] + ... + M[X]
。 S
称为前缀sum 。 F(K)
等于N
加1
减去X
的{{1}}。每次我们将号码S(X) = K
放置到Z
位置时,我们都会将{0}置于N + 1 - X(for K = I[Z] + 1)
。为了更快地找到M[X]
,我可以在X
时间内O(N)
时间使用Binary Indexed Trees计算前缀和,Binary Search来查找O(logN)
X
S(X)
等于某个预定义值。
此算法的总复杂程度为O(N(log(N))^2)
。 This is
Ruby中的实现(您可以在ideone中正确地使用它:更改输入,运行并检查输出):
# O(n(log(n))^2) solution for http://opc.iarcs.org.in/index.php/problems/FINDPERM
# Binary Indexed Tree (by Peter Fenwick)
# http://community.topcoder.com/tc?module=Static&d1=tutorials&d2=binaryIndexedTrees
class FenwickTree
# Initialize array 1..n with 0s
def initialize(n)
@n = n
@m = [0] * (n + 1)
end
# Add value v to cell i
def add(i, v)
while i <= @n
@m[i] += v
i += i & -i
end
end
# Get sum on 1..i
def sum(i)
s = 0
while i > 0
s += @m[i]
i -= i & -i
end
s
end
# Array size
def n
return @n
end
end
# Classical binary search
# http://en.wikipedia.org/wiki/Binary_search_algorithm
class BinarySearch
# Find lower index i such that ft.sum(i) == s
def self.lower_bound(ft, s)
l, r = 1, ft.n
while l < r
c = (l + r) / 2
if ft.sum(c) < s
l = c + 1
else
r = c
end
end
l
end
end
# Read input data
n = gets.to_i
q = gets.split.map &:to_i
# Initialize Fenwick tree
ft = FenwickTree.new(n)
1.upto(n) do |i|
ft.add i, 1
end
# Find the answer
ans = [0] * n
(n - 1).downto(0) do |i|
k = BinarySearch.lower_bound(ft, q[i] + 1)
ans[n - k] = i + 1
ft.add k, -1
end
puts ans.join(' ')
还存在O(N(log(N)))
时间的解决方案。它使用某种Binary Search Tree:我们在椎骨上创建带有数字1, 2, 3, ... N
的BST,然后我们可以在K-th
中找到O(log(N))
个数字,并删除O(log(N))
中的椎骨时间也是。
也可能存在std::set的解决方案,但我现在想不到。
PS。我还建议你阅读一些关于algo和olimpyads的书籍,如Skienna(编程挑战)或Cormen(算法导论)
PPS。对于我之前描述的错误解决方案感到抱歉
答案 1 :(得分:3)
最昂贵的部分显然是在结果数组中移动了多达100 000个元素。
如果将该阵列拆分为更多块,则可以通过一些重要因素加快它的速度。
如果你说10个块并记住每个块的元素数量,你可以根据键选择正确的块来写入,然后只需要为该块移动元素(最多减少10倍)。
新问题是如何在整个数据块中实现良好的数字分配。
您也可以使用链接列表:http://www.cplusplus.com/reference/stl/list/
它在插入方面非常有效,但随意寻找。但仍在寻求的只是读取操作,因此寻找x元素可能比在数组中移动x元素更快(IDK)。
然后你可以使用组合方法并使用多个指针链接列表,所以你总是可以从最近的指针中寻找。
答案 2 :(得分:2)
这是一个非常好的算法以及C ++中所需的编码:
问题是通过以下事实解决的:如果在第7位有2,那么,两个空盒子 在放置7之前离开。因此,如果0为8,而7为7,则反向结果数组看起来 喜欢:8 _ _ 7 _ _ _ _。
现在,完成平方根分解,并完成插入:
#include <iostream>
#include <math.h>
using namespace std;
int main()
{
int n, k = 0, d, r, s, sum, temp, m, diff, check = 1;
cin >> n;
d = sqrt(n) + 1;
int arr[n], result[n], indices[d], arr2[d][d], num = 1;
for (int i = 0; i < n; i++)
cin >> arr[i]; //The inversion sequence is accepted.
for (int i = 0; i < d; i++)
indices[i] = 0; //Indices tell where to start counting from in each row.
for (r = 0; r < d; r++)
{
for (s = 0; s < d; s++)
{
arr2[r][s] = num; //Array is filled with 1 to n (after sqrt decomposition).
num = num + 1;
if (num == n+1)
{
check = 0; break;
}
}
if (check == 0)
break;
}
int l = s;
while (l >= 0) //Non-Zero numbers are shifted to right and 0 placed in
{ //empty spaces.
arr2[r][d-1 - k] = arr2[r][l];
k = k + 1; l = l - 1;
}
k = d-1 - k + 1;
for (int t = 0; t < k; t++)
arr2[r][t] = 0;
indices[r] = indices[r] + k; //Index of the last row is shifted to first non-zero no.
for (int i = n-1; i >= 0; i--)
{
sum = 0; m = 0;
while (sum < arr[i] + 1)
{
sum = sum + (d - indices[m]); //Empty boxes in each row are counted.
m = m + 1;
}
m = m - 1;
sum = sum - (d - indices[m]); //When sum = 1 + corresponding value in inversion
diff = arr[i] + 1 - sum; //sequence, then that particular value is made 0
temp = indices[m] + diff - 1; //and (that value - 1) is the index of the number
//to be placed in result array.
result[arr2[m][temp] - 1] = i+1;
for (int w = temp - 1; w >= indices[m]; w--)
{
arr2[m][w + 1] = arr2[m][w]; //Then, 0 is shifted to just before non-zero number
} //in the row, and the elements are shifted right
arr2[m][indices[m]] = 0; //to complete the sort.
indices[m] = indices[m] + 1;
} //This is done n times for 1 to n integers thus
//giving the permutation in reverse order in result
for (int p = n-1; p >= 0; p--) //array.
cout << result[p] << ' ';
return 0;
}
答案 3 :(得分:1)
您的算法对此问题效率不高,因为您的复杂度为O(n ^ 2),这意味着某些输入案例的操作为10 ^ 10。你必须提出一个更便宜的解决方案。
我建议您使用以下算法(索引从1到N):
for i=1 to N
input a(i)
if i==1 insert 1 into b
else insert i into b at place i-a(i)
end