ETH官方钱包

切換
舊版
前往
大廳
主題

ZeroJudge - a048: 函數增減性 解題心得

Not In My Back Yard | 2018-11-20 20:02:31 | 巴幣 0 | 人氣 254

題目連結:


題目大意:
給定兩正整數 N ( 1 ≦ N ≦ 200, 000 )、 Q ( 1 ≦ Q ≦ 200, 000 )。表示一開始有N個函數,且全部都是遞增的函數。像是F(X),當X越大,F(X)之值越大,則F(X)就是一個遞增的函數;反之,則是一個遞減函數。

而現有 Q 行,每行第一個數字為 V 。

如果 V = 1 ,則接下來有一正整數 i ,代表第 i 個函數的增減性要反過來(增函數變成減函數、減函數變增函數)。

若 V = 2 ,則會接著兩正整數 L 、 R ( 1 ≦ L ≦ R ≦ N ),求:
上述值的增減性(也就是把上述式子看作一個新函數),其中 F代表第 L 個函數,以此類推。遞增則輸出「0」;反之,輸出「1」。



範例輸入:
5 3
2 2 4
1 3
2 2 4



範例輸出:
0
1



解題思維:
首先,我們觀察只有 3 個函數的時候:

一開始都是增函數,所以 F(F(F(X))) 當然也是增函數。

而現在我們把第2個函數翻轉增減性,因此 F(F(X)) 變成了一個遞減函數,所以 F(F(F(X))) 也變成了一個遞減函數。

而再把第3個函數翻轉,因此 F(F(X)) 變回了一個遞增函數,而 F(F(F(X))) 也變回了增函數。

以上我們可以發現:如果把增函數當作1,減函數當作-1。則
的增減性即為各個函數增減性所代表的數字之乘積。像是上面的例子, F(F(F(X))) 之增減性 = 1* (-1) * (-1)= 1 ,因此是遞增的函數。



但是因為 N 、 Q 可以高達 200, 000 ,而如果我們每次詢問增減性的時候就要用迴圈去跑一次乘積,最糟的時間複雜度為 O(NQ) ,然而時間限制為 1 秒。

就算使用前綴預處理的概念,在更新函數的增減性之時,最糟要從 1 跑到 N 。因此最糟的時間複雜度仍然是 O(NQ) 。

因此,我們又要像 d712: The 3n + 1 problem 這題一般去使用「偽線段樹」。只是這次節點儲存的是區間的增減性、更新時也是更新增減性而已。如此一來,便可以在時間範圍內達到所需,時間複雜度為 O(Q * logN)。




此次分享到此為止,如有任何更加簡潔的想法或是有說明不清楚之地方,也煩請各位大大撥冗討論。

創作回應

胖胖貓
Hi 我之前在練習寫線段樹,不過後來爬博客才知道有分成一般線段樹 / 樹狀數組 / zkw式線段樹,但我又看到你寫的偽線段樹0.0,看了一下程式碼看操作方式( Update 和 Query )好像是屬於一般的,希望大大解個惑
這邊補上我爬的資訊:
( 樹狀數組 ) http://www.cppblog.com/menjitianya/archive/2015/11/02/212171.html
( zkw線段 ) https://hk.saowen.com/a/11bb38a671277339d63f480aee8c58395727014b4eeb1407b3ffacae5f2ad48e
2018-11-21 17:08:39
Not In My Back Yard
造成你的誤會實在是不好意思,因為實際上「線段樹」是既有的資料結構,用以儲存一般的「線段」(而不是像「偽線段樹」的 1 ~ N 的二分區間)。

因為一般網路上談論奇怪的黑魔法(?)的時候經常會把「偽線段樹」,省略為「線段樹」。而本人為了分別原有的資料結構,因此把此題用到的資料結構稱為「偽」線段樹。

像是您舉的zkw講的就是「偽線段樹」的進化型態(但是跟樹狀數組一樣,本人看了霧煞煞XD)。因此本人的「偽線段樹」其實等同於網路上常見的「線段樹」(儘管這麼說不太對)。

詳細可以參照網路上的神人(搜尋「演算法筆記」)。

以上。

P.S:雖然會寫「偽線段樹」,可是本人卻不會寫原本的「線段樹」XD
2018-11-21 17:30:24
胖胖貓
感謝大大回應~
對我來說 一般的線段樹結構相似於heap 但是作法很像 Divide & Coquer( 分治法 )
ZJ 的 d798/ d799/ d800 都是這類的 Range Query 的模板題可以練習看看
其他線段樹的題目我還沒發現
2018-11-21 19:37:25
Not In My Back Yard
感謝分享!
2018-11-21 19:50:05
胖胖貓
這邊我再談另一個技巧叫做優化輸入/輸出
一邊我們常使用 cin / cout 搭配取消同步 cstdio的連動指令:
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
這個一般情況下都可以應付,但是今天遇到 Range Query時往往也會有大量的輸入和輸出,以下是自己利用getchar() 一個字元一個字元讀取整數的寫法:(以這題來說光是優化輸入的寫法可以讓時間從 76ms 降低成 44ms,而更少的時間我猜應該是用到 zkw線段樹 搭配優化輸出達成的,博客上對於這類的優化有人提供標準的寫法,查一下就可以找到應付小數或是負數的寫法,甚至是字串等等 )
bool scanInt(int &x){ char c; for(x=0;(c=getchar())>='0' and c<='9';x=(x<<3)+(x<<1)+c-'0'); return c!=EOF; }
2018-11-22 01:56:12

更多創作