2016年1月31日 星期日

[SPOJ MATGAME]MATGAME - Matrix Game

先備知識:$SG$函數的性質

首先可以發現每一列(這裡的「列」是row)可以視作獨立的遊戲,那麼我們就可以分別把每一列的$SG$函數算出來,全部XOR起來,如果是零,那麼先手敗,否則先手勝

再來觀察每一列的$SG$函數怎麼算,為了方便說明,設這一列的第$i$個數字是$A_{i}$,$SG(i)$代表$A_{1}$~$A_{i-1}$都是0時的$SG$函數

經過觀察,可以發現:
$SG(M)=A_{M}$(代表一個經典的$Nim$遊戲)
$if\ A_{i}\leq SG(i+1)$,$SG(i)=A_{i}-1$
$else$,$SG(i)=A_{i}$
(也是一個$Nim$遊戲,只是0顆石頭時的$SG$函數是$SG(i+1)$,不是0)

把所有$SG(1)$XOR起來,判斷是不是0,就完成了

時間複雜度:$O(N*M)$

code:

[IOI Camp Judge 41]農田分配

IOI Camp Judge是暫時性的,因此我做了題目備份

由於分數只會越來越多,所以對於每位助手,在一個時間點之後,分數就永遠在標準以上

因此,我們可以考慮二分搜這個時間點,利用某種方法判斷這時這位助手(以下稱$A$)達到標準了沒
怎麼判斷呢?

我們可以把這時間點之前的每次操作都檢查看看能讓$A$加多少分
因此我們得到一個$O(NQ\log Q)$的算法
怎麼優化呢?

可以發現,如果每位助手同樣都要針對$Q$進行二分搜,那為甚麼不乾脆一起二分搜呢?
因此,我們可以遞迴二分$Q$的區間,找出哪些助手會跑進這個區間裡面
注意,這裡不是真的去二分搜,反而比較像在遍歷線段樹的每個節點
這顆「線段樹(之後稱之Q-Tree)」總共有$O(\log Q)$層,每一層都是$N$個助手(因為每位助手在同一層中只能待在一個時間區間裡面),時間複雜度還是$O(NQ\log Q)$,沒有吃虧,但......我們還可以作些甚麼?

假設某個節點有$n$個助手,我們可以一起處理這$n$個助手的資訊!
怎麼做呢?
有兩種方式:(假設這個節點包含$q$個操作)
1. 枚舉$q$個操作,$O(\log n)$更新每位助手的分數
2. 枚舉$n$位助手,$O(\log q)$求出這位助手的得分

如果你有想到要怎麼用這兩種方式做,拜託告訴我......我不會QQ(遭踢飛

往線段樹的方向思考,我們能不能把每位助手的得分轉換成某種動態的區間和呢?
相信主要的癥結點還是「同一次加分每位助手最多加一次」XD
那我們針對每位助手$A$,不重複地把每次加分操作對應到他的每塊田就好啦(也就是每個加分操作都會剛好對應到$A$的一塊田,先不考慮這塊田有沒有在加分範圍)

我們就對應到「$_{註1}$位置$\geq$加分區間左界的第一塊田」吧,因此每位助手我們都要再送他$M+1$這塊田,才不會出狀況(看看$_{註1}$我說甚麼XD(指))

咦?那我們就枚舉這$n$位助手的田吧,可以發現,這樣的話在Q-Tree的每一層剛好都只會枚舉到$M$塊田,複雜度還是有希望的!
可以發現,每塊田,假設它的位置在$f$,它的得分就是所有左界$\leq f$且右界$\geq f$的加分操作的總分

可以考慮把這些田依照位置排序,加分操作依照左界排序,將田從左到右枚舉,這樣枚舉到位置$f$的這塊田的時候就可以把所有左界$\leq f$的操作依序作好(怎麼作請看$_{註2}$),然後這塊田的得分就是右界在$[f,下一塊主人相同的田的位置)$這個區間內的加分操作的分數總和了!
yee~~~,所以$_{註2}$線段樹是用來詢問和維護,所有右界在$[l,r]$的加分操作的分數總合
簡單一點的話可以用BIT來實現

假設Q-tree中的這個節點包含$n$位助手、$m$塊田、$q$個加分操作,處理這個節點的複雜度就是$O((n+m+q)\log M)$(不計線段樹或BIT的初始化,事實上,透過還原操作就不用再次初始化)

處理Q-tree的每一層花的時間$O((N+M+Q)\log M)$

總時間複雜度:$O((N+M+Q)\log M\log Q)$

code:

[POJ 1741]Tree

先備知識:樹重心

使用樹分治的思想,假設我們把$u$當作根結點,那麼所有路徑就分成了「經過$u$」和「不經過$u$」兩大類,其中「不經過$u$」的路徑可以透過子樹的遞迴來計算,因此我們只需考慮經過$u$的路徑

經過$u$的路徑又分成兩類:以$u$為端點、不以$u$為端點

以$u$為端點的算法:
我們可以先取得從$u$開始,所有長度$\leq K$的路徑,答案就是這種路徑的數目

不以$u$為端點的算法:($n$為目前考慮的子樹上節點的數量,因此我們有$\sum n=N$)
把上述的路徑排序一下,枚舉路徑$p$,二分搜找出有幾條路徑的長度$\leq K-p.length()$,就可以達到$O(n\log n)$的時間複雜度了,注意還要再把都在同一顆子樹的的「路徑對」扣掉,也是一樣的算法
不過......這樣雖然時間複雜度是正確的,不過在POJ上還是會TLE......
可以發現,枚舉的$p$長度會遞增,因此$K-p.length()$會遞減,具有單調性,就不用二分搜了,可以使用另一個遞減的指針達到$O(n)$的複雜度(不過總複雜度還是$O(n\log n)$,因為還有排序)

為了避免複雜度退化,每次根要選樹重心

時間複雜度:$O(N\log^{2}N)$

code:

2016年1月29日 星期五

[Codeforces Beta Round #23]E. Tree (code本體)

題解+$long\ long$實作的code

注意,本題需要使用大數運算,否則會溢位造成除以0的錯誤

時間複雜度:$O(FN^{2}\log N)$($F$為大數乘法的時間複雜度)

code:

[Codeforces Beta Round #23]E. Tree

首先可以知道一個性質,就是刪完邊之後不存在一條長度$>2$的路徑

證明:
可以把路徑中間那條邊拔掉,會變成兩棵樹$a$、$b$,此時有$a.size()\geq 2$且$b.size()\geq 2$,因此拆邊後變成$a.size()*b.size()\geq a.size()+b.size()$

因此,我們可以對每顆子樹(根節點$u$)構造$DP_{0}[u]$、$DP_{1}[u]$和$DP_{2}[u]$,分別代表這顆子樹在某些限制條件下,刪邊之後,可以得到最高的分數
限制如下:
$DP_{0}[u]$:所有連接$u$的邊都必須刪除
$DP_{1}[u]$:如果$u$和子節點$v$之間的邊沒有刪除,那麼$v$的其他邊都要刪除,且$u$的$size$要當作$2$(用來方便計算$DP_{2}$)
$DP_{2}[u]$:沒有限制,所以本題答案為$DP_{2}[0]$(如果根節點為$0$的話)

轉移式如下($v_{i}$代表$u$的子節點,$sz$代表子節點數量):
$DP_{0}[u]$:$\sum_{all\ i}DP_{2}[v_{i}]$
$DP_{1}[u]$:枚舉要連邊的子節點集合$s$,$\max_{all\ s}((\prod_{all\ i}i\in s?DP_{0}[v_{i}]:DP_{2}[v_{i}])*(s.size()+2))$,可以用貪心法將子樹依據$\frac{DP_{0}[v_{i}]}{DP_{1}[v_{i}]}$排序,然後取$\max_{k=0}^{sz}(\prod_{i=1}^{k}DP_{0}[v_{i}])*(\prod_{i=k+1}^{sz}DP_{1}[v_{i}])*(k+2)$,預處理前綴後綴乘積可做到$O(sz\log sz)$,注意這裡要將$u$的$size$當作$2$,也就是上述式子$+2$的由來
$DP_{2}[u]$:$max\{DP_{0}[u],u的size是1時的DP_{1}[u],\max_{all\ i}\frac{(\prod_{all\ j}DP_{2}[v_{j}])*DP_{1}[v_{i}]}{DP_{2}[v_{i}]}\}$

注意,本題需要大數運算,但為了邏輯清楚,以下先給出用$long\ long$實作的code,本code無法AC!

用大數運算實作可AC的code在這裡

時間複雜度:$O(N^{2}\log N)$

code:

2016年1月27日 星期三

[IOI Camp Judge]D. 串珠吊飾

先備知識:後綴陣列及其衍生的$height$陣列

IOI Camp Judge是暫時性的,因此我做了題目備份

因為是環狀,把字串複製成兩倍,又因為反向也要考慮,所以再把整個字串反轉接在後面
因此我們得到一個長度$4N$的字串

求出這個字串的$height$陣列,再用任何可以$O(\log N)$求區間最小值的方式 (仔細想想$height$的定義,會發現$height$的區間最小值剛好對應兩個後綴子串的最長共同前綴長度),線段樹、RMQ等都可以,找出每個對應位置的最長共同前綴長度,取最大值就好
注意一下每個變數的定義和互相轉換的方式

這些對應位置有:
1. $(i,i+\frac{N}{2}), 0\leq i<\frac{N}{2}$(同向匹配)
2. $(i,3N-1-i+1),0\leq i<N$(反向匹配)

注意,如果這個最大值$>\frac{N}{2}$,就輸出$\frac{N}{2}$

p.s.如果你還有良心的話,不要直接複製貼上我的code哦~

code:

[IOI Camp Judge]C. 區間最大連續和

先備知識:Treap (分併樹)、懶惰標記

IOI Camp Judge是暫時性的,因此我做了題目備份

這裡只說明怎麼從子樹維護訊息和懶惰標記的下放,其他的不難就當作大家都會了

一個節點存了以下訊息:
1. 基本的size ($sz$)、priority ($pri$)、value ($v$)
2. 區間總和($sum$)、最長前綴和($lmx$)、最長後綴和($rmx$)、最長區間和($mmx$),其中$lmx$、$rmx$、$mmx$都必須包含至少一個元素

從子樹維護訊息:code說得最清楚,請參閱函式Maintain(Node* &o)~XD

懶惰標記下放:

$tag$代表是否將這個節點的整顆子樹修改為這個節點的$v$

code說得最清楚,請參閱:
函式Node::Modify(const int _v) (修改標記)
函式PutDown(Node* &o) (標記下放)

時間複雜度:$O(M\log N)$

p.s.如果你還有良心的話,不要直接複製貼上我的code哦~

code:

[Codeforces 449D]D. Jzzhu and Numbers

這題可以用排容原理來做

如果序列全部都是0,那答案就是$2^{N}$了
可是在其他的情況我們會多算,那要怎麼扣掉呢?
假如對於每個$s$,我們算出有幾個$A_{i}$符合$A_{i}\&s=s$,叫它$f(s)$好了,那答案就是$\sum_{s=0}^{2^{20}}(-1)^{CntBit(s)}2^{f(s)}$了(取20是因為不管$A_{i}$怎麼&都一定$\leq 2^{20}-1$)

關鍵是怎麼快速算出$f(s)$

我們可以考慮一個二維DP,$DP[i][s]$代表有幾個$A_{i}$,$s$的前$i$個bit中是$1$的,$A_{i}$的那幾個bit也要是$1$,而後面$20-i$個bit,$A_{i}$和$s$要完全相同

因此,我們就可以一個bit一個bit慢慢地算出$f(s)$了

$DP[0][s]$可以直接算
如果$s$的第$i$個bit是$0$,$DP[i][s]=DP[i-1][s]+DP[i-1][s+2^{i-1}]$
如果$s$的第$i$個bit是$1$,$DP[i][s]=DP[i-1][s]$

因此,$DP[20][s]$就是$f(s)$了,應用在上述的排容原理即可算出答案

時間複雜度:$O(N+2^{20}*20)$

code:

2016年1月26日 星期二

[教學]Windows 10的螢幕亮度無法調整了怎麼辦?

出於無聊,將系統語言設定成英文 (雖然我是台灣人),因此圖片皆為英文版的畫面,說明文字將以「英文 (中文)」形式代表英文和其可能的中文,例如以下會出現的「PnP-Monitor(Standard) (PnP監視器 (標準) )」和「Device Manager (裝置管理員)」
英文很差,所以不保證中文翻譯會和實際情況完全相符

相信最近很多人開機後會發現螢幕超~級~亮~,這樣一來使用電腦時眼睛就非常痛苦>_<
鍵盤上調螢幕亮度的組合鍵也沒反應,到控制台找到調亮度的滑桿,可是滑了也沒看到螢幕亮度有變化Orz

怎麼辦呢?

首先說明原因
Win10會強制使用者安裝最新的更新,包括驅動程式,而最近有一種叫做「PnP-Monitor(Standard) (PnP監視器 (標準) )」的驅動程式,無緣無故被當作更新裝上來了!在這個驅動程式下,螢幕好像變成不支援亮度調節的樣子Orz

解決方法:

[Codeforces 448E]Divisors

用一般的$O(\sqrt{N})$的枚舉因數方法遞迴K層就可以了
不過有幾種重要的優化:
1. 如果$K>10^{5}$,直接輸出$10^{5}$個$1$,除非$X=1$
2. 如果在任何一層$X=1$,就直接輸出$1$,以防重複遞迴導致TLE
3. 將因數分解的結果記錄下來(可以用vector紀錄因數存在map裡面),以防超大質數的重複因數分解

時間複雜度:$O(\sqrt{X}\log X)$(質因數分解$\log X$個因數)

code:

[Codeforces 100488M][ACM ICPC 2014-2015]Construct a Permutation

先說答案怎麼構吧,看程式碼就知道了Orz

再來是說明為甚麼它是最大的
回顧我們找LIS(Longest Increasing Subsequence)的過程,DP[i]代表目前長度為i的LIS最後一個元素的最小值
其中,每讀進序列的一個元素,要嘛LIS最長的長度增加,不然就是某個DP[i]被更新變小(因為一個排列中沒有重複的元素)
又可以發現,當某個DP[i]被更新變小的時候,這些曾經在同一個i被存過的元素可以構成某個DS(Descreasing Subsequence)!
因此,把所有這樣產生的DS長度取最大值,叫它M吧,那麼這個序列的LDS(Longest DS)長度$\geq$M
可以發現,如果限制LIS的長度$\leq$A,LDS的長度$\leq$B,那麼i最大可以到A,而每個DP[i]又最多被更新B次,因此可以得到序列長度$\leq$A*B,也就是以下程式碼生出序列的長度!

code:

[UVa 11895]Honorary Tickets

We can use priority_queue to maintain the current best choice
A state contains following information:
1. The expect value of number of lucky tickets (I use a pair of long long, $(u,d)$, to represent a fraction)
2. The total number of envelops (called $c$ later)

The possibility of getting from a bag is $\frac{\frac{u}{d}}{c}$, i.e. $\frac{u}{dc}$

Total time complexity: $O(K\log N)$

code:

2016年1月25日 星期一

[公告]code風景區破千瀏覽量囉!

code風景區瀏覽量破千截圖
  感謝大家的支持,讓code風景區自1/7開站以來瀏覽量首度破千!
  為了讓以後的文章更好,有任何改善建議請隨時留言或寄信給小莫,小莫會盡量在短時間內立即回覆並試用!
  也歡迎各位已經寫過相似性質blog的朋友們來信或留言,我們互相加友情連結吧~^_^

請踴躍發言
請踴躍發言
請踴躍發言
因為很重要所以要說三次

我暫時想不到還有甚麼要說的,就很開心而已XD
就這樣囉,Bye~

Email:fsps60312@yahoo.com.tw

這些一定要再提一下www
自己寫的鑽礦遊戲Digging Game 2  下載版本2.2.9  下載版本2.2.10  原始碼開放囉
幫助小莫牢記上千單字到現在還沒忘記的測驗軟體和它的使用說明(雖然當初是寫給自己用的但是成效非常驚人,就分享給大家用囉~)
我們的粉絲專頁高雄中學105級科學班專題成果發表會《 Möbius 》!!!

[UVa 12906]Maximum Score

For simplicity, we call the sequence that fit the restriction of $x_{i}$ $S_{i}$.
We can know that the length of $S_{i}$ would not exceed number of $x_{j}$, such that $x_{j}\leq x_{i}$
If we sort the numbers in increasing order, all $S_{i}$ would reach the maximum length!
So we can calculate the first answer according to descriptions above.

For the second answer, we know that for each $S_{i}$, all values $<x_{i}$ on the left of $x_{i}$ must form a non-increasing sequence, all values$<x_{i}$ on the right of $x_{i}$ must form a non-decreasing sequence, or $S_{i}$ would not be able to reach the maximum length.
So the optimal permutations are composed of a non-increasing sequence and a non-decreasing sequence.

Let's sort the pairs: $\{(v_{k},f_{k})\}$
The number of such optimal permutations is $\prod_{i=1}^{P-1}  (f_{i}+1)$

If you don't know why, let's call the location of maximum values $peak$.
for every $v_{k}$, we have $f_{k}$ of such numbers, we can decide to put $0,1,2,...,f_{k}$ on the left of $peak$ and $f_{k},f_{k}-1,f_{k}-2,...,0$ on the right of $peak$, so there are $f_{k}+1$ ways to assign values of $v_{k}$. Therefore, you know, the number of ways to assign all $v_{k}$ is $\prod_{i=1}^{P-1}  (f_{i}+1)$. :)

Total time complexity: $O(P\log P)$

Note:
1. The first answer is no need to modulo $10^{9}+7$.
2. The first answer must be handled with unsigned long long, not long long.

code:

[ZJ a764]pC. Tony 與只有神知道的世界

先備知識:Dijkstra

此題就是要你找出最小的環($\geq$三點)
可以發現,只要不走回頭路,走回原點時一定會經過三點以上,因此只要以每個點當原點做Dijkstra,狀態中紀錄上一個走過的點,走回原點之際回傳更新答案就好

時間複雜度:$O(N^{3}\log N)$

p.s.如果狀態用三個int(現在的點、上一個點、走過的距離)存會在最後一筆MLE,我把現在的點和上一個點合起來用一個int存就過了=ㄦ=

code:

[UVa 12581]Divisibility

Let's try to figure out what's the value of $(x_{1},x_{2},x_{3},...)$.
We can discover that the value is the number of different paths from $(0,0,0,...)$ to $(x_{1},x_{2},x_{3},...)$, one step in the path is defined as increase one of the co-ordinates by one.

So we can know the value of $(x_{1},x_{2},x_{3},...)$ is $\frac{(\sum_{k=1}^{N}  x_{k})!}{\prod_{k=1}^{N}  (x_{k}!)}$
If you don't know why, image that there are $x_{1}$ $vectors$ of $(1,0,0,...)$, $x_{2}$ $vectors$ of $(0,1,0,...)$, and so on. Therefore, no matter how you permutate them, they always form a path from $(0,0,0,...)$ to $(x_{1},x_{2},x_{3},...)$, the number of such permutations is $\frac{(\sum_{k=1}^{N}  x_{k})!}{\prod_{k=1}^{N}  (x_{k}!)}$ (Let's call it $M$ later)

The problem is, in which condition $M$ is not divisible by P ?

We can just take only $P$'s multiples into consideration.
The number of factor $P$ in $n!$ is $\left[\frac{n}{P}\right]+\left[\frac{n}{P^{2}}\right]+\left[\frac{n}{P^{3}}\right]+...$
For simplicity, we call the value $f(n)$ (regard $P$ as constant)

Then M is not divisible by P if and only if $f(\sum_{k=1}^{N}  x_{k})=\sum_{k=1}^{N}  f(x_{k})$
i.e., for every $r\in\mathbb{N}$, $\sum_{k=1}^{N}  (x_{k}\mod P^{r})<P^{r}$ must hold.

If we transform every $n$ into $P-dicimal$ numbers, call it $T(n)$, then the above equation holds if and only if $\sum_{k=1}^{N}  (k_{th}$ digit of $T(x_{k}))<P$.

So be question becomes, how many set, $(x_{1},x_{2},x_{3},...)$, are there, such that for each $i\in\mathbb{N}$, $\sum_{k=1}^{N}  (i_{th}$ digit of $T(x_{k}))<P$ ?

Let's try to design a Dynamic Programming method to get the answer.
Suppose that we can enumerate from high digit to low digit.
A state includes the following information:
1. $i$, represent we are considering the $i_{th}$ digit now.
2. For every $T(x_{k})$, the range of its $(i-1)_{th}$ digit should be.(whether the $(i-1)_{th}$ digit has low bound or up bound)

For example, if the $i_{th}$ digit has a low bound of $a$, a up bound of $b$, the $(i-1)_{th}$ digit has no bounds if the $i_{th}$ digit$\in[a+1,b-1]$, has a low bound if the $i_{th}$ digit is $a$, has a up bound if the $i_{th}$ digit is $b$
Thus, we can update the values by enumerate the subsets of its set of bounds.
It's convenient to use bitmask to represent the sets.
Partial time complexity: $O(3^{2N}\log_{P}\max(x_{k}))$
However, we can optimize it by skipping if the value is 0.

Use another DP to calculate the number of cases, such that $\sum_{k=1}^{N} \{ i_{th}$ digit of $x_{k}$, within the given bound$\}<P$.
Partial time complexity: $O(NP)$.

Total time complexity: $O(3^{2N}NP\log_{P}\max(x_{k}))$.

code:

2016年1月24日 星期日

[Codeforces 452E]Three strings

先備知識:後綴陣列和其衍生的$height$陣列

我們可以考慮將$s1,s2,s3$三個字串接起來,中間用兩個不同的特殊字元隔開
那麼,當我們把它的$height$陣列建構出來之後
對於一個裡面都$\geq L$的區間,我們就可以算出裡面有$a$個屬於$s1$,$b$個屬於$s2$,$c$個屬於$s3$,那麼這個區間裡面符合題目條件的$(i1,i2,i3)$數量就是$a*b*c$了

這樣就得到一個$O(N^{2})$的樸素做法了:
枚舉$L$,然後對$height$陣列從左到右掃找出所有內部都$\geq L$的區間,將所有合法區間的$a*b*c$加起來,就是$L$的答案了

再來就是怎麼降低時間複雜度
可以發現,當$L$遞減時,這些合法區間會互相合併變大
因此,可以先將$height$從大到小排序,然後將$L$從大到小枚舉,慢慢加入高度足夠的$height$並使用並查集維護合法區間,在區間合併時,立即算出新區間的$(a,b,c)$,將答案加上$a*b*c$,並把兩舊區間的$a*b*c$扣回去

時間複雜度:$O(N\log N)$($N$是三字串的長度總和)

code:

[Codeforces 558E]A Simple Task

仔細想想,其實這題沒很難
由於字串中只有a~z,因此可以套用基數排序的想法,把排序過的區間用26個數字表示,這樣就可以$O(26)$快速分裂或$O(26)$快速合併這些區間了 (合併相當於把對應的區間排序好)
這裡我利用一個set來維護這些區間的位置

分裂次數$\leq 2Q$(分裂出目標區間),因此合併次數$\leq (N-1)+2Q$

時間複雜度:$O((N+Q)\log N)$

code:

2016年1月21日 星期四

[UVa 12584]Laptop Chargers


Minimum how many chargers does he need to run all the laptops forever:
We can just sum up each discharge rate of the laptops, and then calculate how many chargers do we need to prevent total charge rate from being lower than the total discharge rate.
Explanation:
If total charge rate $<$ total discharge rate, you'll be sure to run out all your batteries someday.
Otherwise, with very fast charger switching, you can try to keep all your batteries from being lower than original conditions, and even better if possible.
For simplicity, we call the answer $\min M$

Maximum how long will he be able to run all his laptops using $M (M < N)$ chargers?
If $M\geq \min M$, the answer is, of course, $-1.000$.
Otherwise, we can come up with a simple method to get an "answer" first. That is, get total charge remaining in the laptop batteries(We call it $TR$ later), total discharge rate of laptops(We call it $TD$ later), and total charge rate of chargers(We call it $TC$ later)
Then we can get $\frac{TR}{TD-TC}$.
What's the problem if we treat it as the answer?
There exists some cases, such that one of the laptop battery is able to sustain for a very long time, even all other laptops have run out of battery with all chargers working.

So the problem is, which laptops don't need to be charged?
We can sort the laptops by the time they run out of battery without charger. For some $m$ ($m\geq M$), we can try use all chargers to make the first $m$ laptops run as long as possible, and see whether the $(m+1)$-th laptop can sustain until the first $m$ laptops are all run out of battery with all $M$ chargers working.

Find the first $m$ that the $(m+1)$-th laptop can sustain to the last. Then the answer is the time when the first $m$ are out of battery.
Otherwise, if we can't find such $m$, the "fake answer", $\frac{TR}{TD-TC}$, mentioned at first is printed.
Notice that if $TC\geq TD$ or $\frac{TR}{TD-TC}>100000$, you should always print "$-1.000$".

Time complexity: $O(N\log N+QN)$

Random inputs had been added to uDebug. :)

code:

2016年1月20日 星期三

[UVa 1465]Searchlights

這題被汝嘉歸類在「進階資料結構」章節,但其實我只有用STL的multiset就過了,至於樹套樹之類的高階資料結構要怎麼應用在這上面,我真的沒有頭緒QAQ

由於「maximum level」和探照燈的覆蓋沒有單調性,因此能得到答案的方式大概就只有枚舉這個「maximum level」了
可是maximum level可能的數值多達10000個,而燈泡數又可以到1000000個,時間可不夠每次重算哪......怎麼辦呢?

為了方便說明,簡寫maximum level為$ML$
可以想像,在$ML$慢慢增加的時候,燈泡會一個一個「關掉」(因為不夠亮),而剩下的燈泡都會覆蓋四方$ML$長度的十字架區,而行與行、列與列之間又都是互相獨立的,因此可以分開來個別維護「直線上相鄰兩燈泡的最長距離」,看看任兩相鄰燈泡能不能都照亮它們之間的格子
當然,頭尾兩端是特殊情形,要分開考慮

可以用set維護直線上那些位置有燈泡,再用multiset維護這些燈泡將直線分成那些長度的區間。要考慮兩端的特殊情形,就把兩端的區間長度設為$inf$以免影響答案,再取出最左和最右的燈泡位置進行計算即可

當$ML$從小到大慢慢枚舉時,刪除燈泡的操作相信也不難想到要怎麼利用set和multiset的特性來維護,唯獨實作上容易出BUG而已XD

時間複雜度:$O(MN(\log N+\log M+\log (MN)))$

這裡提供一些測資讓你debug用:

3 3
2 0 2
0 2 0
2 0 2

3 3
2 0 2
0 1 0
2 0 2

3 3
1 1 1
1 0 1
1 1 1

3 3
2 2 2
2 0 2
2 2 2

4 4
2 2 2 2
2 0 0 2
2 0 0 2
2 2 2 2

5 5
2 2 2 2 2
2 0 0 0 2
2 0 0 0 2
2 0 0 0 2
2 2 2 2 2

code:

[TIOJ 1841][POI 21 Stage 1]好.傳囉! Nice Boat!

(題號打錯所以只好重發文章@@,順便套用剛學的latex語法)
因為HOJ在我寫完這題之後的submissions都沒有動靜(聽說HOJ快要壽終正寢了,好難過QAQ),所以現在來寫TIOJ了XD

這題題目敘述有點雜,還誤會題目意思打錯code,花了不少時間才理解成功@@
就是,給你一個數列,求最長區間的長度,使得這個區間的任意前綴和後綴和都$\geq 0$

首先,區間的任意前綴和後綴和都是區間和,可以用數列的前綴和(不是區間的)相減求得
假設一開始的$0$也算進去的話,我們可以把這些前綴和畫成有$N+1$個點的折線圖

而假設有一個區間符合題目的條件,把它對應到折線圖上面
那麼仔細思考就可以發現,在折線圖的這個區間裡面,區間左端是最低點,右端是最高點,中間不會允許任何一個點突破兩端的極值(不然就會有區間前綴或後綴和$<0$了)

為了方便說明,定義規則1為區間中間的值必須$\leq$右端點,規則2為區間中間的值必須$\geq$左端點
我們可以先預處理出每個左端點$u$符合規則2中最右邊的右端點$R[u]$,可以用單調對列實現。可以證明,在左端點到$R[u]$之間的所有點,都會是符合規則2的右端點
再來,對於每個左端點,可以維護一個符合規則1的右端點的集合,這一樣可以用單調對列從右到左掃實現
這樣,當左端點維護到$u$時,我們就可以在這個集合裡面進行二分搜,在符合規則1的右端點之中找出最大且$\leq R[u]$的點$v$,那麼$[u,v]$就是符合題目要求中左端點是$u$的最長區間了
在這些計算結果中,取最大的區間長度即可

時間複雜度:$O(N\log N)$

code:

[TIOJ 1840]Coding Days コーディングデイス


這題彩蛋還真多......例如把滑鼠移到某個"coding days"會突然跳出一張圖片XD

這題可以用線段樹來維護,每個節點存的是經過排序後的數列,這樣每次詢問時就可以直接對值域做二分搜,把相關的$\log N$個節點找出來相加算出這個值的排名,當作二分搜的比較基準

由於要支持修改操作,因此每個節點的有序數列不能直接用陣列,否則修改會TLE。這裡我用Treap實現名次樹,因此整個資料結構就是「線段樹套名次樹」

至於插入操作,題目再三叮囑你要詳讀題目敘述
題目敘述中可以看到這一行:「......我們相信如果紀錄裡出現了插入的動作,我們也只要輸出一行7122......」
所以如果出現操作3,直接輸出一行"7122"就好了(還好CBD有提醒不然我就!@#%^&*...

對了先將值域離散化可以加速,像我的code就可以用1950ms的時間踩過2000ms的限制(?)

設值域大小為$M$
初始化複雜度:$O(N\log M)$
操作1複雜度:$O(\log N{\log}^{2}M)$
操作2複雜度:$O(\log N\log M)$
操作3複雜度:$O(1)$
總時間複雜度:$O(Q\log N{\log}^{2}M)$

code:

2016年1月18日 星期一

[UVa 12345]Dynamic len(set(a[L:R]))

先備知識:Linked-List

我參考了Morris的做法
首先,把數據離散化,這樣就可以對每一個v用陣列儲存對應的資訊了
當然,用map動態增點也可以

定義:
A[i]為題目給的陣列
S[i]是一個Linked-List的節點,連接了上一個和下一個相同數字的位置

先來看看回答一個區間有幾種不同數字可以用甚麼資訊求出答案,假設現在輸入"Q x y+1"(這樣[x,y]就是閉區間了)
最難的就是排除重複了
如果我們對於每一個數字A[i]儲存它左邊第一個相同數字的位置,假設叫L[i]好了
那麼只要算算看,對於x<=i<=y,有幾個L[i]<x就是答案了(因為如果有重複的,第二次出現之後它的L[i]都會>=x)

所以問題變成了動態區間求小於某數的有幾個,可以用sqrt(N)個長度為sqrt(N)的有序數列(存L[i])來維護,這些有序數列就是code裡面的LEFT

詢問時:
用lower_bound查詢整塊,兩端如果還沒覆蓋完就用S[i]的資訊暴力枚舉

修改時:
假設輸入"M x y",起初A[x]=z
可以先刪除z這個點,再新增y這個點
實作上可以想成「將S[x]從儲存z的資訊的Linked-List裡面取出,再接進儲存y的資訊的Linked-List裡面」
為啥用Linked-List?
因為這樣才能夠在執行Linked-List操作的同時維護LEFT
另外,對於每一個值,還要儲存它們分布在A的那些地方(code裡面的LOCS),這樣才知道要插入Linked-List的哪個位置

更詳細請看code或自己想XD

時間複雜度:O((N+M)sqrt(N))

code:

#include<cstdio>
#include<cassert>
#include<vector>
#include<algorithm>
#include<cmath>
#include<set>
using namespace std;
struct Query
{
    char type;
    int x,y;
};
vector<Query>QUERY;
int N,M,A[50000];
void ReadInput()
{
    int querycount;
    scanf("%d%d",&N,&querycount);
    for(int i=0;i<N;i++)scanf("%d",&A[i]);
    QUERY.clear();
    while(querycount--)
    {
        static char type[2];
        static Query q;
        scanf("%s%d%d",type,&q.x,&q.y);
        q.type=type[0];
        QUERY.push_back(q);
    }
}
void Discretize()
{
    vector<int>v;
    for(int i=0;i<N;i++)v.push_back(A[i]);
    for(const Query &q:QUERY)if(q.type=='M')v.push_back(q.y);
    sort(v.begin(),v.end()),v.resize(unique(v.begin(),v.end())-v.begin());
    M=v.size();
    for(int i=0;i<N;i++)A[i]=lower_bound(v.begin(),v.end(),A[i])-v.begin();
    for(Query &q:QUERY)
    {
        if(q.type=='M')q.y=lower_bound(v.begin(),v.end(),q.y)-v.begin();
        else q.y--;
    }
    v.clear(),vector<int>().swap(v);
}
struct Node
{
    Node *l,*r;
    const int loc;
    Node(const int _loc):l(NULL),r(NULL),loc(_loc){}
};
void Insert(vector<int>&s,const int v)
{
    const int sz=s.size();
    auto it=lower_bound(s.begin(),s.end(),v);
    assert(it==s.end()||v<=(*it));
    if(it!=s.begin())assert((*--it)<v),it++;
    s.insert(it,v);
    assert((int)s.size()==sz+1);
}
void Erase(vector<int>&s,const int v)
{
    const int sz=s.size();
    auto it=lower_bound(s.begin(),s.end(),v);
    assert(it!=s.end()&&(*it)==v),s.erase(it);
    assert((int)s.size()==sz-1);
}
Node *S[50000],*BEGIN[100000],*END[100000];
set<int>LOCS[100000];
vector<int>LEFT[224];
int GAP;
void Insert(Node* &l,Node* &o,Node* &r)
{
    assert(l->r==r&&r->l==l);
    if((r->loc)<N)Erase(LEFT[(r->loc)/GAP],l->loc),Insert(LEFT[(r->loc)/GAP],o->loc);
    Insert(LEFT[(o->loc)/GAP],l->loc);
    l->r=o,o->r=r;
    r->l=o,o->l=l;
}
void Extract(Node* &o)
{
    Node *l=o->l,*r=o->r;
    if((r->loc<N))Erase(LEFT[(r->loc)/GAP],o->loc),Insert(LEFT[(r->loc)/GAP],l->loc);
    Erase(LEFT[(o->loc)/GAP],l->loc);
    l->r=r,r->l=l;
    o->l=o->r=NULL;
}
void PreProcess()
{
    assert(M<=100000);
    Node **pre=new Node*[M];
    for(int i=0;i<M;i++)
    {
        pre[i]=BEGIN[i]=new Node(-1),END[i]=new Node(N);
        BEGIN[i]->r=END[i],END[i]->l=BEGIN[i];
        LOCS[i].clear();
    }
    GAP=min(N,(int)sqrt(N)+1);
    for(int i=0;i<=(N-1)/GAP;i++)LEFT[i].clear();
    for(int i=0;i<N;i++)
    {
        const int v=A[i];
        LOCS[v].insert(i);
        Insert(pre[v],S[i]=new Node(i),END[v]),pre[v]=S[i];
    }
    delete[]pre;
}
int main()
{
//    freopen("in.txt","r",stdin);
//    freopen("out.txt","w",stdout);
    ReadInput();
    Discretize();
    PreProcess();
    for(const Query &q:QUERY)
    {
        if(q.type=='M')
        {
            Node* &o=S[q.x];
            Extract(o);
            LOCS[A[q.x]].erase(q.x);
            auto itr=LOCS[q.y].upper_bound(q.x),itl=itr;
            Insert(itl==LOCS[q.y].begin()?BEGIN[q.y]:S[*--itl],o,itr==LOCS[q.y].end()?END[q.y]:S[*itr]);
//            printf("%d-(%d,%d)-%d\n",o->l->loc,o->loc,q.y,o->r->loc);
            LOCS[q.y].insert(q.x);
            A[q.x]=q.y;
        }
        else if(q.type=='Q')
        {
            const int l=(q.x+GAP-1)/GAP,r=(q.y+1)/GAP-1;
            assert((l-1)*GAP<q.x&&q.x<=l*GAP);
            assert((r+1)*GAP-1<=q.y&&q.y<(r+2)*GAP-1);
            int ans=0;
            for(int i=l;i<=r;i++)
            {
                const vector<int>&s=LEFT[i];
                ans+=lower_bound(s.begin(),s.end(),q.x)-s.begin();
            }
            if(q.x/GAP==q.y/GAP)
            {
                for(int i=q.x;i<=q.y;i++)if((S[i]->l->loc)<q.x)ans++;
            }
            else
            {
                for(int i=q.x;i<l*GAP;i++)if((S[i]->l->loc)<q.x)ans++;
                for(int i=q.y;i>=(r+1)*GAP;i--)if((S[i]->l->loc)<q.x)ans++;
            }
            printf("%d\n",ans);
//            assert(ans==q.y-q.x+1);
        }
        else assert(0);
//        for(int i=0;i<=(N-1)/GAP;i++)
//        {
//            printf("[%d]:",i);
//            for(const int v:LEFT[i])printf(" %d",v);puts("");
//        }
    }
    return 0;
}

2016年1月17日 星期日

[HOJ 303]買醬油IV 之 商店問題

HOJ生病惹QQ,因此我在這裡做了題目備份,請參考~

先備知識:最長嚴格遞增子序列、lower_bound和upper_bound的定義

為了方便說明:
1. 縮寫「最長嚴格遞增子序列」為LSIS
2. 定義S[i]為第i家商店的醬油價格
3. 定義L[i]為以S[i]為結尾的LSIS長度
4. 定義ANS[i]為以S[i]為結尾的LSIS中花費的最小精力

這裡假設你已經知道怎麼算每個L[i]
可以把我們的任務想成先求出最長的LSIS長度MAXL,並找出L[i]=MAXL的前提下,ANS[i]的最小值

那麼ANS[i]怎麼算呢?
現在,假設我們算到第i家商店,而我們也知道ANS[1~i-1],並算出L[i],可以考慮從L[0~i-1]之中找出所有L[j]=L[i]-1且S[j]<S[i],取最小的ANS[j],加上i便是ANS[i]了

為了降低時間複雜度,可以考慮對於每個長度LEN,維護一個二元組(LSIS結尾元素,最小精力)的集合,方式是在插入一個值之前,利用類似單調對列的概念,刪除所有不可能是答案的點,取最小值時只要取upper_bound的前一個就好了,可以用map來實作

注意花費的精力可能會爆int(每家店都拜訪需要5000050000),因此相關的數據要用long long處理

時間複雜度:O(NlogN)

ps.在我AC這題之後,HOJ馬上就掛了Orz

code:

2016年1月16日 星期六

[HOJ 300][Codechef]Party Invitation

HOJ生病惹QQ,因此我在這裡做了題目備份,請參考~

這題我漏考慮了圖不連通的情況卡了一小時QAQ

假設圖是連通的
先把這張圖當有向圖來看,可以發現,由於每個點最多也只能連一條邊出去,因此整張圖就一定會有一個環,然後旁邊有可能有小樹枝指向這個環(指出去的小樹枝是不可能的,因為這樣環上的點就需要兩條出邊了)
找環的方式很簡單,只要隨便找一個點,順著唯一的有向邊走,當走到重複的一個點時,就可以把這點當環的起點了

先來講點題外話
這題就是要問你一張圖有幾種獨立集
獨立集是甚麼?就是一個點集,在圖上沒有一條邊兩端點都在這個點集裡面

如果題目給的圖是一棵樹,獨立集數量要怎麼算呢?
轉成有根樹後,我們可以使用Dfs+記憶化搜索,令DP[u][c]為根節點為u的這棵子樹的獨立集數量,c=0代表u不能選進獨立集,c=1代表u一定要選進獨立集
因此,我們得到轉移式:
DP[u][0]=Pi{DP[v][0]+DP[v][1],v是u的子節點}(Pi是把每個東西乘起來的結果)
DP[u][1]=Pi{DP[v][0],v是u的子節點}

如果題目給的圖是一個環,獨立集數量要怎麼算呢?
先把環的每個點依序存到CYC裡面
令DP[u][l][r]為從CYC[0~u]裡面選出獨立集的數量,l代表CYC[0]要不要選進獨立集,r代表CYC[u]要不要選進獨立集
一開始,可以先知道DP[0][0][0]和DP[0][1][1]都是1,DP[0][0][1]和DP[0][1][0]都是0
那麼u>0時就可以得到轉移式:
DP[u][l][0]=DP[u-1][l][0]+DP[u-1][l][1]
DP[u][l][1]=DP[u-1][l][0]
最後,答案就是DP[N-1][0][0]+DP[N-1][0][1]+DP[N-1][1][0](N是環的大小)

疑真巧,這題給的圖剛好就是環上面接幾棵樹耶XD
那就來對環作DP吧
先把環的每個點依序存到CYC裡面
為了方便說明,TREE[u][c]代表接到CYC[u]上面的這棵樹裡面選出獨立集的數量,c代表CYC[0]要不要選進獨立集
TREE[u][c]怎麼算上面說過了XD
令DP[u][l][r]為從CYC[0~u]以及「接到這些點上面的樹」裡面選出獨立集的數量,l代表CYC[0]要不要選進獨立集,r代表CYC[u]要不要選進獨立集
一開始,可以先知道DP[0][c][c]是TREE[0][c],DP[0][0][1]和DP[0][1][0]都是0
那麼u>0時就可以得到轉移式:
DP[u][l][0]=TREE[u][0]*(DP[u-1][l][0]+DP[u-1][l][1])
DP[u][l][1]=TREE[u][1]*DP[u-1][l][0]
最後,答案就是DP[N-1][0][0]+DP[N-1][0][1]+DP[N-1][1][0](N是環的大小)

這題實作上沒有甚麼特別複雜的地方,code也很容易寫得出來,傳上去也WA了(?)
咦?
還記的我開頭講甚麼嗎?
這張圖不一定連通!XD

因此,分別將每個連通子圖(可以用並查集判斷)的獨立集數量算出來後,相乘就是答案了(因為它們互相獨立)
喔還有,要記的開long long和模1000000007

code:

[HOJ 299]比特集合

HOJ生病惹QQ,因此我在這裡做了題目備份,請參考~

對於Insert、Delete、Add操作,只要先設定一個時間基準,假設是西元T年好了,然後每次Insert或Delete操作前先回推到西元T年時這隻小雞幾歲,然後在這個時間下作操作就好,Add的話就現在時間增加一年,實作上可以只用一個int變數來維護時間差
複雜度:使用map的話Insert和Delete都是O(log n(小雞)),Add是O(1)

相信大家會卡的應該是Q-bit操作
首先可以注意到Q-bit操作的d不會大於15,因此儲存數量級2^(d+1)的資料是可行的
想像一下,如果我們維護模2^(d+1)是v歲的小雞數量,假設是cnt[v]好了,把所有的cnt[v]都存下來,那麼答案就是Sum{cnt[v],v=2^d~2^(d+1)-1}了

於是乎問題轉換成動態求區間和了
因此我們可以考慮維護16個樹狀數組,第i個樹狀樹組維護的是d=i的情況下,所有cnt[v]的資訊,由於會有Add的操作,所以在對樹狀數組進行操作之前都要先轉換成西元T年的數據,算出對應的區間,Query其中的Sum就好
注意,區間有可能被切成兩段,視情況而定

時間複雜度每次操作不大於O(log n(小雞))

code:

[HOJ 297][NOIP 2010 提高組]引水入城

HOJ生病惹QQ,因此我在這裡做了題目備份,請參考~

這題看似簡單,但要想出時間複雜度夠低的做法不太容易
我們可以考慮一個DP[i][j]存二元組(l,r)代表(i,j)這個點最左邊能夠到達第l行的水庫,最右邊能夠到達第r行的水庫,如果這個點無法到達任何水庫,則(l,r)=(INF,-INF)

經過觀察,可以發現,對於任何兩個沙漠城市a,b(a在b的左邊),如果a能夠到達最右邊的水庫的位置>=b能夠到達最左邊的水庫的位置,那麼a和b的這兩條水路一定會有交叉,換句話說,a和b可以共用同一個水庫!

反之,則a和b無法共用相同的水源

因此,我們只要求出所有的DP[N][i],1<=i<=M,就可以透過一個貪心策略,將i從1到M跑一次,如果一個點(N,i)可以到達最左邊的水庫的位置>目前啟用最右邊的水庫的位置,就把這點能夠到達最右邊的水庫啟用

這樣,如果中途出現DP[N][i]=(INF,-INF),就改為計算幾個點也是(INF,-INF),代表有幾座乾旱區中的城市不可能建有水利設施,不然的話,依照上述策略啟用了幾座水庫就代表最少建造幾個蓄水廠

再來就是DP怎麼算了

首先,因為旁邊的高度要嚴格小於這裡的高度水才會流過去,因此不會有環狀鬆弛的問題,因此對於每個DP[i][j],可以用DP[y][x]來更新,其中(y,x)必須在(i,j)旁邊且高度比(i,j)還高
這裡使用記憶化搜索較為方便,注意在到達河邊的時候DP的初始值要設為(c,c)而不是(INF,-INF)

時間複雜度O(N*M)


code:

[HOJ 294][Codeforces #192]The Evil Temple and the Moving Rocks

HOJ生病惹QQ,因此我在這裡做了題目備份,請參考~

總之,這題是各顯神通吧,只要你的方法可以讓石頭碰撞夠多次就OK
我想到的方式是類似下面這樣:

v.<.<.<.<.<<<<<<<
>>>>>>>.>.>.>.>.^
v.<.<.<.<.<<<<<<<
>>>>>>>.>.>.>.>.^
v.<.<.<.<.<<<<<<<
>>>>>>>.>.>.>.>.^
v.<.<.<.<.<<<<<<<
>>>>>>>.>.>.>.>.^
v.<.<.<.<.<<<<<<<
>>>>>>>.>.>.>.>.^
v.<.<.<.<.<<<<<<<
>>>>>>>.>.>.>.>.^
v.<.<.<.<.<<<<<<<
>>>>>>>.>.>.>.>.^


把魔法施在左上角那顆往下的石頭

經過模擬:
N=3時,會發出聲響3次
N=5時,會發出聲響6次
N=90時,會發出聲響81044次
N=100時,會發出聲響108949次


剛好都卡底線XD

code:

[HOJ 293][Codeforces #192]Graph Reconstruction

HOJ生病惹QQ,因此我在這裡做了題目備份,請參考~

可以發現,因為每個點的度數都<=2,因此分岔是不可能存在的,所以整張圖就是: 一些點、幾條線、幾個圈,其中點可以視為大小1(長度0)的線
總之,先把這些線和圈找出來,存起來,假設待會會用到(?)
先來討論圈,因為看起來比較簡單(?)
一個圈在甚麼情況下可以變成另外一個圈又符合題目指定的條件呢?
首先可以發現,圈的大小<=4時是不行的,不然的話:
1. 如果圈的大小是奇數
可以在圈上每隔兩格把點連起來,這樣跑完兩圈剛好回到原點,也把所有的點都經過了
2. 如果是偶數
可以從1兩格兩格跳到n-1,再跳到2,然後回到n,再兩格兩格跳回4

線的話也可以照做

我們先來盡量找出合法的邊,並盡量拼成一個圈(可以證明圈是符合條件中最好(邊數最大化)的選擇之一)

現在,假設我們拼出了大小n的合法圈,那麼所有長度<=n的線和圈都可以merge進這個大圈裡面了(方法是將圈的每一個邊拆開,接上目標的每一個點)
所以,我們可以先把這些線和圈依照大小作排序,大的排前
如果第一個線或圈就可以自己變成合法的圈,可以發現,接下來因為大小是遞減的,照著上面的方法一個一個merge完就全部結束了

如果第一個線或圈不能自己變成新圈,那麼可以考慮和第二個線或圈合併討論,或連第三個也一起加進來......,好像變得很複雜= =

我們可以將邊分為兩類:不合法邊(也就是原圖的邊,程式碼中Edge.type=0)和合法邊(Edge.type=1)
因此,可以構造一個策略,先將線和圈的邊用不合法的身分push進queue裡面,然後對於之後的線或圈,就可以用「以一個點和一條不合法邊換取兩條合法邊」的策略了
仔細想想就會發現更好的策略是不可能的

請注意以下狀況:
如果是大小4的圈,轉換成不合法邊*2+合法邊*2是最優的
    (不合法:(1,4),(2,3),合法:(1,3),(2,4))
如果是大小4的線,轉換成不合法邊*1+合法邊*3是最優的
    (不合法:(2,3),合法:(1,3),(1,4),(2,4))
如果是大小3的線,轉換成不合法邊*2+合法邊*1是最優的
    (不合法:(1,2),(2,3),合法:(1,3))
剩餘的,都只能轉換成不合法邊*線或圈的大小

如果最後不合法邊都不見了,那麼就可以保證答案不是-1(想一想,為甚麼XD)
不然的話,把不合法邊都pop掉,看看剩餘的合法邊數量有沒有大於M
結束(好難解釋啊QAQ)

對了,這裡提供一個簡單的測資讓你debug用:
4 3
1 2
2 3
3 4

code:

2016年1月15日 星期五

[HOJ 287][Codeforces #145]Practice

HOJ生病惹QQ,因此我在這裡做了題目備份,請參考~

可以想像,一開始所有的蘿莉都黏在一起
每比一場比賽,就好像把黏在一起的蘿莉「切開」
而最後的目標是:要把蘿莉切成變一隻一隻的
(好恐怖><)
首先可以知道,最後一定是切剛好N-1刀,每一回合可以切幾刀端看現在有幾團蘿莉
可以發現,如果有任何一回合沒有切開所有團,那麼一定是吃虧
那問題就變成要怎麼讓每一回合蘿莉的團數盡量大
當然就是平分啦(證明呢?想一想,為甚麼XD 提示:可以往二元樹去想)
所以只要寫一個程式模擬切蘿莉的過程,在切每一團蘿莉時都盡量平分,然後記錄下中間結果
最後一次輸出答案即可

code:

[UVa 12590]Guards II

(Bottom is the English version)
這題的關鍵在排容原理
由於角落的守衛的特殊性(可以一次覆蓋兩條邊界)而且只有四個,我們不妨枚舉角落守衛的分配情況,然後再看看甚麼情況下可以覆蓋到所有邊界格子
定義:
Cover0(N,M,K):N*M長方形中放K個守衛使得第1行到第M行都有守衛,符合條件的情況數量
Cover1(N,M,K):和Cover0一樣,只是左上角那個點不能放守衛
Cover2(N,M,K):和Cover1一樣,只是連右上角也不能放守衛(所以左上角和右上角都不能放)
以上的函數計算時間必須在O(max(N,M))以內

情況一:四個角落都沒有守衛
我們可以先計算四條邊界都至少有一位守衛的情況數量,這裡我使用排容原理
如果有一條邊界完全沒有守衛,假設是下邊界好了,有一些例外狀況是合法的:上面從左到右的每一行都至少有一個守衛
所以就用人海戰術把下邊界完全覆蓋起來了
這種例外狀況的數量是Cover2(N,M,K)
注意,和上邊界沒守衛的例外狀況會有重複的情況,因此相加之後要扣掉
重複情況的數量是Cover0(N,M,K)
另一個方向N、M互換就好

情況二:只有其中一個角落有守衛
為了方便說明,我們只討論守衛放在左上角的情況
一樣,我們可以先用排容原理計算右邊界和下邊界都至少有一位守衛的情況數量
然後也有一些例外狀況允許下邊界完全沒有守衛:上面從第2行到第M行都至少有一個守衛(因為第1行已經有角落的守衛了)
這種例外狀況的數量是Cover2(N,M,K)+Cover1(N,M,K)
另一個方向N、M互換就好

情況三:其中兩個相鄰的角落有守衛
為了方便說明,我們只討論守衛放在左上角和右上角的情況
一樣,我們可以先用排容原理計算下邊界至少有一位守衛的情況數量
也有一些例外狀況允許下邊界完全沒有守衛:上面從第2行到第M-1行都至少有一個守衛
這種例外狀況的數量是Cover2(N,M,K)+2*Cover1(N,M,K)+Cover0(N,M,K)
另一個方向N、M互換就好

情況四:其中兩個相對的角落有守衛
就算沒多放守衛也已經覆蓋所有邊界格子了
隨便亂放,數量為C(N*M-4,K-2)(四個角落已經決定好了要扣掉,而且已經用掉了兩個守衛)

情況五:其中三個角落有守衛
就算沒多放守衛也已經覆蓋所有邊界格子了
隨便亂放,數量為C(N*M-4,K-3)(四個角落已經決定好了要扣掉,而且已經用掉了三個守衛)

情況六:四個角落都有守衛
不想講了XD

注意到,上述的Cover0、Cover1、Cover2和C在計算過程中會被使用很多次,使用記憶化搜索可以大幅提升效率

上面的六個數字加權後加起來,就是答案了
別忘了隨時模1000000007以免爆long long

時間複雜度O(N*M*max(N,M)*K)

(English version)

The key point is inclusion-exclusion principle.

Because of the particularity of corner cells (if you place a guard here, he can cover two lines of border) and there are only four such cell, we can enumerate the allocation of corner guards, and then discuss, at what circumstances all border cells can be covered.
Definitions:
Cover0(N,M,K):place K guards in N*M grid, such that each column from 1-st to M-th has at least one guard. The return value is the number of circumstances that meet the restrictions above.
Cover1(N,M,K):Same as Cover0, but you can't place a guard at the up-left cell.
Cover2(N,M,K):Same as Cover1, but you can't place a guard at the up-right cell(So you can place guard in neither up-left cell nor up-right cell).
Time complexities of functions above shouldn't exceed O(max(N,M)).

Case 1:All four corners are empty

First, we can calculate the number of circumstances, such that each of four lines of border has at least one guard, here I use inclusion-exclusion principle.
If there exist a line of border that has no guard, suppose it's down border.
There are still some exceptions that down border can be fully covered.
That is, each column has at least one guard exists.
So the down border is fully covered with human wave tactics.
The number of such exceptions is Cover2(N,M,K).
Notice that this will have duplicates with the exceptions of no guard on the up border, so after adding them together, we must deduct such duplicates.
The number of such duplicates is Cover0(N,M,K).
For another direction, just swap N and M, and handle it the same way.

Case 2: Only one corner has guard

For convenience, we only consider the guard to be placed at up-left corner.
Same as above, we can use inclusion-exclusion principle to get the number of circumstances, such that both down border and right border has at least one guard.
Also, there are exceptions that allow a empty down border.
That is, each column from 2-nd to M-th has at least one guard(Because column 1 already has a guard on the corner).
The number of such exceptions is Cover2(N,M,K)+Cover1(N,M,K).
For another direction, just swap N and M.

Case 3: Two of the adjacent corners has guard

For convenience, we only consider the guard to be placed at up-left corner and up-right corner.
The same, we can use inclusion-exclusion principle to get the number of circumstances, such that down border has at least one guard.
Again, there are exceptions that allow a empty down border.
That is, each column from 2-nd to (M-1)-th has at least one guard.
The number of such exceptions is Cover2(N,M,K)+2*Cover1(N,M,K)+Cover0(N,M,K).
For another direction, just swap N and M.

Case 4: Two of the opposite corners has guard

All border cells are covered even if you haven't placed any on it.
Place guards as you like, the number of circumstances is C(N*M-4,K-2)(Four corners has been decided in advance and need to be excluded, and we've already used two guards).

Case 5: Three of the corners has guard

All border cells are covered even if you haven't placed any on it.
Place guards as you like, the number of circumstances is C(N*M-4,K-3)(Four corners has been decided in advance and need to be excluded, and we've already used three guards).

Case 6: All of the four corners has guard
I don't want to say that again. XD

Please be noted that
Cover0, Cover1, Cover2, and C might be calculated many times, so it's recommend to use memorized search to improve performance.

Weight the six numbers above and then plus them together, this should be the answer

Don't forget to module 1000000007 at any time for fear of overflowing~

Time complexity: O(N*M*max(N,M)*K)

code: