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年3月6日 星期日

質數的線性篩法

我們希望每個合數只被篩到一次
可以發現,對於每一個合數$N$
設它的最小質因數為$P$
一定有$\frac{N}{P}\geq P$
而且$P\leq \frac{N}{P}最小的質因數$
因此,我們可以從小到大枚舉$Q$ ($\frac{N}{P}$)
所有$\leq Q最小的質因數$的質數$P$去更新$PQ$
實作上枚舉$P$的時候發現$P$整除$Q$時跳出迴圈即可

code:

[SPOJ COT3]COT3 - Combat on a tree (優化後的code)

請先參考這裡的題解

優化1:
$trie$的新節點從動態宣告改成靜態宣告 ($replacement\ new$)

優化2:
$trie$單字結尾的訊息使用鏈表來儲存,做到$O(1)$合併
原本是用$vector$儲存,啟發式合併做到整體均攤$O(N\log N)$,現在變成了$O(N)$
你覺得這沒有差?
嘿嘿嘿
還是要做這個優化才能卡進時限喔XD

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

code:

[SPOJ COT3]COT3 - Combat on a tree

題目連結

如果要看懂這個題解,請先了解組合賽局的$SG$函數和$Nim$和的性質

這題的盤面感覺很複雜
不妨先假設所有點都是白色的吧

還是好複雜?
不妨先從一個點來討論吧

一個點的$SG\ value$當然就是$1$囉

然後拓展到兩個點
$SG\ value$變成$2$了

拓展到三個點
如果三個點成鏈狀,$SG\ value$當然就是$3$了
如果是櫻桃形的,$SG\ value$可以照以下方式算出 ($\oplus$是$XOR$的符號)
可以得到$SG\ value=mex\{0,1,1\}=2$

一般的,我們可以用以下方式來算出以任何一個點為根的子樹的$SG\ value$ (注意這裡假設每個點都是白色的)
(之後將通通以這張圖做參考)
注意到選了$4$這個節點之後,整棵樹分裂成了$4$個子遊戲
選了$4$之後整個盤面的$SG\ value$就是這些子遊戲的$SG\ value$的$Nim$和 ($\oplus$)
我們只要枚舉$11$種選擇,就可以算出這個盤面的$SG\ value$
也就是$mex\{選了i之後整個盤面的SG\ value,1\leq i\leq 11\}$

利用記憶化搜索,再加上一些避免重複計算的小技巧,可以做到時間複雜度$O(N^{2})$
很可惜的,這樣當然還不夠好

說明:
子樹$u$為以$u$為根的子樹
$SG(xxx)$為盤面為$xxx$的$SG\ value$

如果現在要算$SG(子樹1)$,而且正在枚舉子樹$4$的點
可以發現,$SG(子樹1選擇8後的盤面)=SG(子樹4選擇8後的盤面)\oplus SG(子樹3)\oplus SG(子樹2)$
因為正在枚舉子樹$4$的點,所以$SG(子樹3)\oplus SG(子樹2)$可以視為常數!

這樣,如果我們用某種資料結構存下計算$SG(子樹4)$時的中間結果 (也就是$SG(子樹4選擇u後的盤面)$)
如果這個資料結構支持在短時間內
1. 將全部資料$\oplus$一個數字
2. 算出全部資料的$mex$
3. 插入一個數字

再套用啟發式合併,就解決了
$copy-on-write$的二元$trie$,搭配懶惰標記,剛好符合所有需求!
實作上並不是那麼的困難,就不細講了
提示:把每個數字轉換成二進位,然後當作$01$字串來看
另外,我的啟發式合併有點隱形,請參考函數Node *Merge(Node *a,Node *b,const int depth)

這樣就做完了嗎?
好像還沒耶

我們假設每個點都是白色的
現在有些點可能是黑色的

其實......仔細想想,那些黑色的點直接忽略就好了
具體實現要用什麼樣的方式忽略
請參考下面的code吧

你認為這份code就可以拿去AC了嗎?
好像還沒耶哈哈
這份code常數過大會導致TLE,優化的方法和code在這裡

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

code:

2016年3月5日 星期六

[Codeforces 317D]Game with Powers

題目連結

如果要看懂這個題解,請先了解組合賽局的$SG$函數和$Nim$和的性質

不曉得您有沒有看出,$2$的冪次方組成的數字可以當作是獨立的子遊戲呢?
同理
$3$的冪次方組成的數字也可以當作獨立的子遊戲
$4$的冪次方就不能了,因為$4$已經被歸類在$2$的冪次方
$5$的冪次方可以
$6$的冪次方也可以
......

好像知道了甚麼
我們只要算出這些子遊戲的$SG\ value$,然後$\oplus$起來就好了嘛XD ($\oplus$是$XOR$的符號)
可以發現,如果某個子遊戲裡面只有一個數字,那麼它的$SG\ value$就一定是$1$了
我們只要關心以$2\sim\sqrt{N}$為底的子遊戲,它的$SG\ value$怎麼算就好了

可以發現,每個子遊戲最多包含$30$個數字
而這個遊戲的底數是甚麼並不重要,我們只需關心剩下的數字分別是幾次方
因此,可以簡單地用$bitmask$+離散記憶化搜索 (我使用了$map$) 來計算
可是,這樣還不夠快

可以發現,我們要求$SG\ value$的狀態,表示成$bitmask$永遠是一堆$1$組成
因此,可以先將$2^{1}-1$、$2^{2}-1$、$2^{3}-1$、$2^{4}-1$、...、$2^{30}-1$的答案在本機先算好 (參見// for(int i=0;i<=30;i++)printf("%d %d\n",(1<<i)-1,GetSG((1<<i)-1));),然後直接寫進程式碼就好了

要記得排除$\sqrt{N}\sim N$中已經被算進前面的子遊戲的數字
如果算出來有奇數個數字,就把答案$\oplus 1$
還有
$1$自己也是一個獨立的子遊戲,要再把答案$\oplus 1$

答案為$0$時先手敗,否則先手勝

時間複雜度:$O(\sqrt{N}\log N)$ (經過本機的預計算,$SG\ value$的取得可以當作$O(1)$了)

code:

[Codeforces g100365F][ASC 34]Coins Game

原文題目敘述 (第7頁)
傳code

題目敘述:
桌上擺了N*M個硬幣 (位置從 (1,1) 到 (N,M) ),一開始知道哪些硬幣頭朝上,哪些數字朝上
每一回合必須選一個頭朝上的硬幣 (假設這個硬幣的位置是 (i,j) ),再選個位置 (a,b),其中0<=a<i且0<=b<j,然後把 (a,b)、(a,j)、(i,b)、(i,j) 這四個位置的硬幣都翻轉
注意到,當a=0或b=0的時候,有些要翻轉的位置是不存在的,所以在這些位置的翻轉操作是無效的
當玩到最後每個硬幣都是數字朝上,沒有步可以動了,就輸了

故事背景:
某校的學生餐廳是孳殉懊淋霹啞界聞名的 (以下簡稱學餐)
有時食物吃下去會發現又酸又澀的酒味,然後就知道,又要拉肚子一整天了
還有一次吃高麗菜結果被魚刺卡喉嚨,馬上緊急送醫 (造成食道傷口,還好沒傷到喉嚨)
我們從此對學餐感到恐慌,因此合力爭取到晚餐不吃學餐的權利 (不過預算只有100元,而且午餐還是無法倖免)
然後這是某次培訓時想出來的遊戲
因為需要很多很多的硬幣鋪在桌上,所以想要申請經費補助
但是管經費的說他為了讓我們不吃學餐幫我們墊錢 (100元*4人*21天),郵局帳戶見底了,所以拒絕請求
我們只好寫程式用電腦來模擬這個硬幣遊戲了
後來覺得太無聊,寫了AI來對打
注意到,國手雖然很可憐每天都要被逼著吃學餐,可是他們寫出來的AI都是無懈可擊的
請問誰會贏?
(真實事件改編)
(N<=50,M<=50)

以下是題解

如果要看懂這個題解,請先了解組合賽局的$SG$函數和$Nim$和的性質

首先,每個頭朝上的硬幣可以視為一個獨立的遊戲
提示:想想$Nim$和的性質,翻轉硬幣相當於$XOR$ (還是一頭霧水?請參考這裡)

因此,我們就可以用$O(N^{2}M^{2}\log(NM))$的時間預處理出每個位置的$SG\ value$ (因為我計算$mex$的方法牽涉到排序,其實利用基數排序還可以把$\log$去掉),然後就可以算出整個盤面的$SG\ value$了!

題目要求當先手贏的時候要輸出一種必勝策略
只要用$O(N^{2}M^{2})$的時間枚舉$i$、$j$、$i_{1}$和$j_{1}$,找到一組解讓$SG(i,j)\oplus SG(i_{1},j)\oplus SG(i,j_{1})\oplus SG(i_{1},j_{1})=$整個盤面的$SG\ value$就好了 ($\oplus$是$XOR$的符號)
對了,還有一個條件
你只能考慮一開始的盤面中位置$(i,j)$的硬幣是頭朝上的$(i,j)$

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

code:

[Codeforces 603C]Lieges of Legendre

題目連結

題目敘述

給你N堆石頭 (原題中的cow) 和數字$K$,我們來玩一個遊戲
操作1:拿掉任一堆的任一顆石頭
操作2:選個$2n$顆石頭的石頭堆,換成$K$個各有$n$顆石頭的石頭堆
不能動就輸了,請問先手勝還是後手勝?

題解

如果要看懂這個題解,請先了解組合賽局的$SG$函數和$Nim$和的性質

可以發現,每堆牛都可以視作獨立的遊戲 (想一想,為甚麼XD)
又可以發現,當$K$是偶數時,可以視作$K=0$,當$K$是奇數時,可以視作$K=1$
因為在算$Nim$和 ($XOR$) 的時候會兩兩對消

我們當然可以使用$O(a_{i})$的記憶化搜索算出每個$a_{i}$的$SG$函數值
也就是code中被註解掉的//int GetSG(const int n)
注意到
因為操作最多兩種,因此$SG$函數值只會在$[0,2]$的範圍內

再來怎麼加速呢?
找規律吧

會發現
當$K\mod 2=0$,$SG(i)=\{0,1,2,0,1,0,1,...\}$,後面皆是$01$循環
當$K\mod 2=1$,$SG(i)=\{0,1,0,1,2,0,2,0,1,...\}$,後面奇數項均為$0$,偶數項均為$1$或$2$

要判斷$K\mod 2=1$時$SG(x)$ ($x\mod 2=0$) 是$1$還是$2$,只要算算看$SG(\frac{x}{2})$是不是$1$即可
這樣複雜度為$O(\log x)$

時間複雜度:$O(\sum_{i=1}^{N}\log a_{i})$

看不懂?可參考Coding Beans的題解

code: