顯示具有 UVa 標籤的文章。 顯示所有文章
顯示具有 UVa 標籤的文章。 顯示所有文章

2016年3月7日 星期一

[UVa 11802]All Your Bases Belong to Us

題目連結

這題問你有幾種進位法,使的$N!$後面有$K$個$0$

假設你已經知道十進位的時候要怎麼算後面幾個$0$
可以發現,當要計算$B$進位時
我們可以先把$B$質因數分解
假設$B$分解後變成$2^{p_{1}}*3^{p_{2}}*5^{p_{3}}*7^{p_{4}}*...$
$N!$分解後變成$2^{q_{1}}*3^{q_{2}}*5^{q_{3}}*7^{q_{4}}*...$
(每個$q_{i}$可在$O(\log N)$的時間內算出來)

那麼$B$進位時$N!$就有$\min_{i=1}^{\infty}\left\lfloor\frac{q_{i}}{p_{i}}\right\rfloor$個$0$了
別被那個$\infty$嚇到,因為$i$到一個數字以後$p_{i}$就全部都是$0$了,也就是可以忽略

現在題目給你$N$和$K$,問你有幾種$B$
也就是有幾種$B$讓$\min_{i=1}^{\infty}\left\lfloor\frac{q_{i}}{p_{i}}\right\rfloor=K$

可以發現,每一項$\left\lfloor\frac{q_{i}}{p_{i}}\right\rfloor$都有一個下限$K$,但又不能全部$>K$
換句話說,就是至少要有一個$\left\lfloor\frac{q_{i}}{p_{i}}\right\rfloor=K$,其他的可以是$\geq K$的隨意值

我們可以使用排容原理
算出讓每個$\left\lfloor\frac{q_{i}}{p_{i}}\right\rfloor$都$\geq K$的$B$有幾種
再減掉讓每個$\left\lfloor\frac{q_{i}}{p_{i}}\right\rfloor$都$>K$的$B$有幾種

可以發現每個$\left\lfloor\frac{q_{i}}{p_{i}}\right\rfloor$都是獨立的 (想一想,為甚麼XD)
因此將每個$\left\lfloor\frac{q_{i}}{p_{i}}\right\rfloor$的合法情況數量乘起來就好 (別忘了$mod$一下)

合法情況數量怎麼算呢?
可以發現$q_{i}$是固定的,我們只需調整$p_{i}$即可
讓$\left\lfloor\frac{q_{i}}{p_{i}}\right\rfloor\geq K$的最大$p_{i}$就是$\left\lfloor\frac{q_{i}}{K}\right\rfloor$
讓$\left\lfloor\frac{q_{i}}{p_{i}}\right\rfloor>K$的最大$p_{i}$就是$\left\lfloor\frac{q_{i}}{K+1}\right\rfloor$

答案就是$\prod_{i=1}^{\infty}(\left\lfloor\frac{q_{i}}{K}\right\rfloor+1)-\prod_{i=1}^{\infty}(\left\lfloor\frac{q_{i}}{K+1}\right\rfloor+1)$
一樣,別被那個$\infty$嚇到,只要發現$\left\lfloor\frac{q_{i}}{K}\right\rfloor=0$時直接跳出迴圈即可

這樣會不會超時啊......
題目保證$\frac{N}{K}<500$
因此當質因數枚舉到$\geq 502$時,$q_{i}=\sum_{k=1}^{\infty}\left\lfloor\frac{N}{502^{k}}\right\rfloor<\sum_{k=1}^{\infty}\frac{N}{502^{k}}=\frac{N}{502(1-\frac{1}{502})}=\frac{N}{501}<K$
於是$\left\lfloor\frac{q_{i}}{K}\right\rfloor$就$=0$了

時間複雜度:$O(Q*91\log N)$ ($\leq 500$的質數有$91$個)

code:
#include<cstdio>
#include<cassert>
#include<algorithm>
#include<vector>
#include<map>
using namespace std;
typedef long long LL;
const LL MOD=1e9+7;
LL N,K;
vector<LL>P;
int main()
{
//    freopen("in.txt","r",stdin);
    P.push_back(2LL),P.push_back(3LL);
    for(int i=2,j;;i++)
    {
        P.push_back(P[i-1]);
        do
        {
            P[i]+=2LL;
            for(j=0;P[j]*P[j]<=P[i]&&P[i]%P[j]!=0LL;j++);
        }while(P[i]%P[j]==0LL);
        if(P[i]>=500LL)break;
    }
//    printf("%d\n",(int)P.size());
//    for(const LL &v:P)printf("%lld\n",v);
    int testcount;scanf("%d",&testcount);
    while(testcount--)
    {
        scanf("%lld%lld",&N,&K);
        LL ans1=1LL,ans2=1LL;
        for(int i=0;;i++)
        {
            assert(i<(int)P.size());
            LL power=0LL;
            {LL n=N;while(n)(power+=(n/=P[i]));}
            const LL &up=power/K,&down=power/(K+1LL);
            if(up==0LL)break;
            (ans1*=(up+1LL))%=MOD;
            (ans2*=(down+1LL))%=MOD;
        }
        static int kase=1;
        printf("Case %d: %lld\n",kase++,(ans1-ans2+MOD)%MOD);
    }
    return 0;
}

2016年2月9日 星期二

[UVa 1613][POJ 3969]K-Graph Oddity

假設某個點的度數$<K$,那麼我們就乾脆先把其他點著色,再來決定這點要著甚麼顏色就好啦

可以發現,本題條件限制$K$和$N$都要是奇數,因此一定有一點的度數$<K$ (想一想,為甚麼XD)
找到這點後,把它從圖上移除,為剩餘的圖著色,這時相鄰的點的度數一定都$<K$,再把它移除,為剩餘的圖著色,......

這樣下去,會剩下一個點,把它塗成顏色1,然後慢慢把刪除的點加回去並著色,這樣整張圖就著色完成了!

把點移除真麻煩?
其實不用真的移除啦

我們只需要從某個度數$<K$的點開始dfs,當走到$u$時,先把$u$的子節點著色 (往$u$的子節點dfs),再把$u$著色

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

p.s.重度優化後在POJ上還是TLE,但是在UVa上獲得第一名惹Orz
p.s.後來在網路上發現網友的解法,還真的可以在POJ上AC,他是用bfs的逆順序著色 (吧?),是說......POJ有這麼恨遞迴嗎!@#%^&*......

code:

2016年2月6日 星期六

[UVa 1502][ACM-ICPC 2011]GRE Words

這題應該算是滿不錯的題目

我想到可以用後綴陣列來解,可是傳上去卻瘋狂的RE
我在想,會不會有空串呢 (如果有的話也太奸詐XD)?
因此,我寫了以下程式來測試:

[UVa 1106][ACM-ICPC World Finals 2011]Machine Works

這裡提供另外一種作法,選個你喜歡的去實現吧~

首先,先把機器依照購買時間排序,那麼就可以設計一個$DP$,$DP[i]$代表到達第i台機器的購買時間之前,賣掉所有機器可獲得的最大收入

為了方便說明,定義$M_{i}$為排序後的第i台機器
$M_{i}.day$代表這台機器的購買時間
$M_{i}.price$代表這台機器購入的價格
$M_{i}.resell$代表這台機器賣出的價格
$M_{i}.profit$代表這台機器每天能產生的利潤

可以發現,當購入第$i$台機器並開始無止盡的運作,你的錢隨著時間變化會是一條斜率$>0$的斜直線,而這條斜直線會和$x=Mi.day$交叉於$y=DP[i]+(Mi.resell−Mi.price)$,因此截距是$DP[i]+(Mi.resell−Mi.price)−Mi.profit∗Mi.day$ (看看DP的定義,想一想,為甚麼XD)

首先,如果我們知道$DP[a]、DP[a+1]、...、DP[b]$,我們就可以拿第$a\sim b$台機器賺最多的錢去分別更新第$b+1\sim N$台機器的最大值了
$_{註1}$考慮使用單調凸包優化$DP$,把第$a\sim b$台機器依照賺錢速度 (斜率) 排序,這樣一來,和這個做法不一樣的是,我們多了斜率的單調性,不用再做麻煩的二分搜和平衡樹動態增刪值了!

透過$deque$ (之後會發現只用到$stack$的功能,因為可以先做好凸包,才開始更新) 實作凸包優化$DP$,我們可以實現複雜度$O(n+m)$(假設拿$n$台機器去更新之後的其中$m$台機器)的更新

問題是,要用哪些機器來更新哪些機器呢(才不會讓複雜度退化)?

我們使用操作分治的思想
首先,如果我們知道了$DP[1]$,就可以拿去更新$DP[2]$ ($DP[2]$確定了)
我們知道了$DP[1]$和$DP[2]$,就去更新$DP[3]$和$DP[4]$ ($DP[3]$確定了)
這下$DP[3]$也確定了,因此拿去更新$DP[4]$ ($DP[4]$確定了)
這下我們知道了$DP[1]$、$DP[2]$、$DP[3]$和$DP[4]$,就去更新$DP[5]$、$DP[6]$、$DP[7]$和$DP[8]$ ($DP[5]$確定了)
......

有點倍增的感覺對不對?
可以發現,以上的做法可以看做是在一棵線段樹上依照遍歷順序,拿左子樹包含的區間 (可以保證這時左子樹的所有$DP$都已經確定了,想一想,為甚麼XD) 去一口氣更新右子樹包含的區間

知道了用甚麼順序更新,使用$_{註1}$講的方法,處理線段樹 (我覺得這裡還是要強調一下,這棵「線段樹」只是提供想像上的框架,不用真的建出來) 的每一層的時間複雜度是$O(N)$ (想一想,為甚麼XD)

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

code:

2016年2月5日 星期五

[UVa 1106][ACM-ICPC World Finals 2011]Machine Works

不知道為甚麼這個人這麼坎坷,總之我寫完後debug了一下就AC了

這裡提供另外一種作法,選個你喜歡的去實現吧~

首先,先把機器依照購買時間排序,那麼就可以設計一個$DP$,$DP[i]$代表到達第$i$台機器的購買時間之前,賣掉所有機器可獲得的最大收入

為了方便說明,定義$M_{i}$為排序後的第$i$台機器
$M_{i}.day$代表這台機器的購買時間
$M_{i}.price$代表這台機器購入的價格
$M_{i}.resell$代表這台機器賣出的價格
$M_{i}.profit$代表這台機器每天能產生的利潤

可以發現,當購入第$i$台機器並開始無止盡的運作,你的錢隨著時間變化會是一條斜率$>0$的斜直線,而這條斜直線會和$x=M_{i}.day$交叉於$y=DP[i]+(M_{i}.resell-M_{i}.price)$,因此截距是$DP[i]+(M_{i}.resell-M_{i}.price)-M_{i}.profit*M_{i}.day$(看看$DP$的定義,想一想,為甚麼XD)

因此,我們需要在一堆直線中,對於特定的$x$,找$y$的最大值
如果我們維護了一個下凸包,那$y$的最大值不就在這個凸包上嗎?

因此,我們需要維護哪些直線是在這個凸包上面
我們可以慢慢的把直線一條條加入,於是有了以下幾種情形:
1. 不影響凸包
2. 截掉凸包的一部分,有些線可能會被從凸包移除

加入線時,需要對斜率進行二分搜,因此開一個map維護斜率對應直線
詢問某個$x$的最大值時,需要對兩兩直線的交點的$x$座標進行二分搜,因此再開一個map維護交點對應直線

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

code:

2016年2月1日 星期一

[UVa 12371]Guards

這題$N$好大,$K$好小,疑那我們能不能構造出$O(NK)$的算法呢XD

考慮$DP[n][k]$代表$n*n$的正方形分成$k$群的答案
於是乎我們得到以下邊界條件:
$if\ n<2k$,$DP[n][k]=0$
$if\ n=2k$,$DP[n][k]=\prod_{i=1}^{k}(2i-1)\binom{2i}{2}$
其中,$DP[2k][k]$可以用$DP[2(k-1)][k-1]*(2k-1)*\binom{2k}{2}$遞推出來(不過直接算也是OK,反正$k$只到50而已嘛XD)

再來考慮$n>2k$時的情況怎麼從$DP[n-1][k]$算出來
可以發現,當我們加入新的一行和一列後,固定新列在最底下,新行則有$n$個插入位置,假設插入後的位置是$x_{0}$好了
那麼,我們可以在$(x_{0},n)$放個守衛,再隨意找一個$x_{1}\neq x_{0}$,在$(x_{1},n)$再放個守衛,這時第$x_{1}$行變成了三個守衛,分別在第$y_{0}$、$y_{1}$、$n$列
怎麼辦呢?
只要從$(x_{1},y_{0})$、$(x_{1},y_{1})$中選一個(假設選$y_{i}$)守衛解雇,然後在$(x_{0},y_{i})$這個位置新增一個守衛就好了(還記的第$x_{0}$行是新行只有一個守衛吧XD)

上述計算方式會有重複,也就是第$x_{0}$行和第$x_{1}$行是對稱的,因此要除以2
我們得到$DP[n][k]=DP[n-1][k]*\frac{2n(n-1)}{2}$

可是...會發現,第$n$行的那群守衛所屬的守衛群大小(定義守衛群大小為其覆蓋的行數或列數)一定$>2$(因為是和另一個守衛群合併)
那第$n$行所屬的守衛群大小$=2$的情況怎麼辦?
可以從$DP[n-2][k-1]$來轉移!
這次,我們一次加入兩行兩列,這些行和列自成一個大小為2的守衛群
一列要固定在第$n$行,另一列則有$n-1$種選擇
兩行有$\frac{n(n-1)}{2}$種排列方式
因此當$k>1$時,$DP[n][k]$還得再加上$DP[n-2][k-1]*(n-1)*\binom{2i}{2}$

使用記憶化搜索實現應該會比較方便

時間複雜度:$O(NK)$

code:

2016年1月26日 星期二

[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日 星期一

[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:

[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月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:

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月15日 星期五

[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: