[英]How to create array [1, 2, 3, 1] from given range Q queries with N elements each [1, 3] [2, 3] [3, 4] in less than O(QN)?
[英]How to add, multiply n numbers to a given range in array and also reverse the range in the array in O(n) time?
假設我們有一個數組L [] = {4,1,2,6}。 我們需要做的是將一個由字符A,R,M組成的字符串S作為輸入,並應用以下算法:
for i from 1 to N do
if ith letter of S is 'R'
reverse L[i...N]
else if ith letter of S is 'A'
add A to all numbers of L[i..N].
else if ith letter of S is 'M'
multiply B to all numbers of L[i..N].
for all number in L[i..N], module them by C.
print L[i]
end
如何優化此算法以使其在O(n)時間內運行? 數組的長度以及字符串是n。 無論如何,在這個算法中的任何優化(如刪除循環以僅添加和乘法)都將受到歡迎。
這個答案很長,但是我已經用相當多的解釋以一種相當容易理解的方式寫了它,所以請跟我說一下。
假設A
和B
都是整數,可以在O(N)
時間內解決這個問題。 否則你可以在此時停止閱讀。 但我認為有必要將算法分解為幾個不同的步驟,每個步驟都是O(N)
。 所以它仍然是O(N)
整體。
可能問題中最困難的部分是弄清楚如何在線性時間內運行此步驟:
if ith letter of S is 'R'
reverse L[i...N]
如果我們只是繼續盯着原始算法,我們將確信即使其他步驟可以在線性時間內實現,這個步驟也永遠不能在線性時間內完成。 但事實並非如此。 我們該怎么做呢? 我想到的方法是從雙端隊列/雙端隊列數據結構中借用一個想法。 由於我們知道數組L
有多長,我們只保留3個變量, leftmost
, rightmost
和isReversed
。
leftmost
將保持L
數組當前最左邊未使用的索引,因此leftmost
的初始化為1
,因為我們正在使用一個索引數組,如你的問題中所述(術語'unused'將在后面解釋)。 rightmost
將保持L
數組當前最右邊未使用的索引,因此初始化為N
,即L
的長度。 isReversed
用於指示陣列是否正在反轉。 這被初始化為false
。 我們手頭的第一個任務是在應用所有reverse
操作后計算出數組L
的原始元素的最終順序。 我們甚至不需要反轉陣列一次以達到與反轉相同的效果。 這可以通過遍歷輸入串S
一次,並且在所有反向操作之后確定陣列L
哪個元素應該在每個位置來完成。 為簡單起見,我們創建了一個新的整數數組L'
將舉行最后原創要素L
將所有的反向操作之后,並試圖填補L'
。
假設我們在索引i
,並且S[i] == 'R'
,所以我們設置isReversed = true
表示我們正在反轉子陣列[i..N]
。 當isReversed == true
,我們知道子陣列[i..N]
正在被反轉,因此L'[i]
處的元素應該是最右邊的未使用元素,其索引rightmost
。 因此我們設置L'[i] = L[rightmost]
,並且rightmost
減 1
( rightmost = rightmost - 1
)。 相反,如果isReversed == false
我們不會反轉子[i..N]
,因此L'[i]
處的元素應該是最左邊的未使用元素,其索引leftmost
。 因此,我們設置L'[i] = L[leftmost]
,和增量 leftmost
由1
( leftmost = leftmost - 1
)。 后續reverse
將否定isReversed
的值。
因此,當前的算法在C ++中看起來像這樣(我假設你對C ++沒問題,因為你的一個問題的標簽是C ++):
// set up new array L'
int Lprime[N+1];
int leftmost = 1;
int rightmost = N;
bool isReversed = false;
for (int i = 1; i <= N; i++) {
if (S[i] == 'R') {
// negate isReversed
isReversed = !isReversed;
}
if (isReversed) {
Lprime[i] = L[rightmost];
rightmost = rightmost - 1;
} else {
Lprime[i] = L[leftmost];
leftmost = leftmost + 1;
}
}
確認這是正確的,雖然我認為是這樣。
現在我們來看看原始算法的其余部分:
else if ith letter of S is 'A'
add A to all numbers of L[i..N].
else if ith letter of S is 'M'
multiply B to all numbers of L[i..N].
for all number in L[i..N], module them by C.
困難的部分似乎是需要在子陣列[i..N]
為每個索引i
執行C
模數。 但基於我有限的理解,這是模運算,我們並不需要在每個i
子陣列[i..N]
上執行它。 但是不要相信我的話。 我對數論的理解非常簡陋。
不僅如此,還可以簡化添加和乘法的步驟。 這里的技巧是保留2個額外的變量,讓我們稱它們為multiplicativeFactor
和additiveConstant
。 multiplicativeFactor
用於保存我們需要乘以L'[i]
的常數。 這最初是1
。 該additiveConstant
變量,顧名思義,是用來存儲任何常量,我們需要添加到L'[i]
相乘后L'[i]
由multiplicativeFactor
完成。 additiveConstant
被初始化為0
。
為了更具體地看待這個,讓我們設置A = 3
, B = 5
。 假設S
是字符串"AMMAAM"
。 這意味着以下( 注意:我們現在忽略模數C
):
1
,設置L'[1] = L'[1] + 3;
2
,設置L'[2] = (L'[2] + 3) * 5;
3
,設置L'[3] = ((L'[3] + 3) * 5) * 5;
4
,設置L'[4] = (((L'[4] + 3) * 5) * 5) + 3;
5
,設置L'[5] = ((((L'[5] + 3) * 5) * 5) + 3) + 3
6
,設置L'[6] = (((((L'[6] + 3) * 5) * 5) + 3) + 3) * 5
觀察到先前字符'A'
和'M'
“繼承”(或級聯)到L'
的未來元素中。 讓我們稍微改變一下這些操作:
L'[1] = L'[1] + 3
L'[2] = 5 * L'[2] + (3 * 5)
L'[3] = 5 * 5 * L'[3] + (3 * 5 * 5)
L'[4] = 5 * 5 * L'[4] + (3 * 5 * 5 + 3)
L'[5] = 5 * 5 * L'[5] + (3 * 5 * 5 + 3 + 3)
L'[6] = 5 * 5 * 5 * L'[6] + (3 * 5 * 5 + 3 + 3) * 5
我們開始看到一些模式。
L'[i]
的乘法因子總是B
的冪。 添加A
對此乘法因子無任何影響。 乘法因子存儲在我們上面描述的multiplicativeConstant
變量中 L'[i]
乘以一個額外的B
,需要將所有常數(由加A
引起)乘以B
以獲得最終常數以加到L'[i]
。 這是上述additiveConstant
變量的目的。 L'[i]
乘法應該在將additiveConstant
加到L'[i]
因此,每個L'[i]
的最終值可以表示為multiplicativeConstant * L'[i] + additiveConstant;
,算法的第二個主要部分如下所示:
int multiplicativeConstant = 1;
int additiveConstant = 0;
for (int i = 1; i <= N; i++) {
if (S[i] == 'A') {
additiveConstant += A;
} else if (S[i] == 'M') {
multiplicativeConstant *= B;
// need to multiply all the constants by B as well
additiveConstant *= B;
}
Lprime[i] = multiplicativeConstant * Lprime[i] + additiveConstant;
}
有一點需要注意,我沒有談過。 multiplicativeConstant
和additiveConstant
整數溢出 ,以及中間計算。 如果L
是一個int
數組,我們很幸運,因為我們可以使用long long
來避免溢出。 否則,我們必須小心中間計算不會溢出。
那么modulo C
操作呢? 實際上,他們將L'[i]
中的每個值保持在[0..C-1]
范圍內。 基於我對數論的有限理解,我們可以像這樣執行模運算來達到同樣的效果:
int multiplicativeConstant = 1;
int additiveConstant = 0;
for (int i = 1; i <= N; i++) {
if (S[i] == 'A') {
additiveConstant = (additiveConstant + (A % C)) % C;
} else if (S[i] == 'M') {
multiplicativeConstant = (multiplicativeConstant * (B % C)) % C;
// need to multiply all the constants by B as well
additiveConstant = (additiveConstant * (B % C)) % C;
}
Lprime[i] = ((multiplicativeConstant * (Lprime[i] % C)) % C + additiveConstant) % C;
}
這解決了multiplicativeConstant
和additiveConstant
變量的溢出問題(但不會阻止中間計算和其他變量的溢出),並完成我們的算法。 我相信這是正確的,但請親自驗證。 我無法解釋模塊化算術的東西,因為我只知道如何使用它,所以你必須自己查找。 另外, A % C
和B % C
部分可以完成一次,結果存儲在變量中。
最后,把所有東西放在一起
// set up new array L'
int Lprime[N+1];
int leftmost = 1;
int rightmost = N;
bool isReversed = false;
for (int i = 1; i <= N; i++) {
if (S[i] == 'R') {
// negate isReversed
isReversed = !isReversed;
}
if (isReversed) {
Lprime[i] = L[rightmost];
rightmost = rightmost - 1;
} else {
Lprime[i] = L[leftmost];
leftmost = leftmost - 1;
}
}
int multiplicativeConstant = 1;
int additiveConstant = 0;
// factor out A % C and B % C
int aModC = A % C;
int bModC = B % C;
for (int i = 1; i <= N; i++) {
if (S[i] == 'A') {
additiveConstant = (additiveConstant + aModC) % C;
} else if (S[i] == 'M') {
multiplicativeConstant = (multiplicativeConstant * bModC) % C;
// need to multiply all the constants by B as well
additiveConstant = (additiveConstant * bModC) % C;
}
Lprime[i] = ((multiplicativeConstant * (Lprime[i] % C)) % C + additiveConstant) % C;
}
// print Lprime
這總體上在O(N)
時間內運行。
再一次,如果你擔心整數溢出,假設L
是一個int
數組,你可以使用long long
來計算所涉及的所有變量,你應該沒問題。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.