CodeForces - 850C Arpa and a game with Mojtaba(博弈+sg函数)
題目鏈接:點擊查看
題目大意:給出n個數,兩個人輪流按照規則操作,不能操作的人即為失敗,規則很簡單,每次找一個質數p的k次冪,記做x=,將數組中所有包含x倍數的數都除以x,必須保證至少有一個數可以除以x,如此往復
題目分析:因為涉及到了質數,第一反應是唯一分解定理,這個題難就難在該如何轉換模型,其實熟悉尼姆博弈的大佬們一眼就能將這個題目轉換為多個子游戲之和,回到這個題目,我們可以將這個題目轉換為以每個質數為子游戲的博弈,最后異或一下每個質數所代表的sg函數,再判斷一下結果就行了
具體的話,因為每個子游戲都不能互相影響答案,所以選擇n個數中的每一個數作為子游戲肯定是不合適的。因為每一次操作都會對其他不同的數字進行操作,那我們可以考慮一下關于每個質數的冪次,比如當前質數p的冪次:1,2,3....k,和另一個質數pp的冪次:1,2,3....k,是互不影響的,所以我們可以將每個質數當做一個子游戲
有了子游戲,我們就該設置sg函數了,因為sg函數是基于當前狀態和后續狀態才能轉移的,所以我們需要考慮每個狀態的后續狀態該如何轉移,其實很好實現,比如還是假設,當前質數p在n個數中總共出現過的冪次有1,1,2,3,5,那么顯然,兩個1的貢獻我們可以合并成一個1的貢獻,因為當我們對于n個數同時除以p的1次冪時,兩個1都會受到相同的影響,從這里我們可以得到多個相同的冪次都可以壓縮為一個冪次的貢獻,再然后我們可以枚舉每一個位上的貢獻,就可以得到后續狀態了,比如我們假設當前枚舉到了k=3,我們該如何計算貢獻的冪次為3下的后續狀態呢:
這樣對于冪次為3的貢獻就將1,1,2,3,5變為了1,1,2,0,2
另外對于sg遞歸寫法最終要的就是邊界問題了,顯然,當狀態全部變為0時,該狀態的sg值一定是0,返回0即可
還有需要強調的一個細節就是,因為每個狀態的sg值都是固定的,雖然前置狀態不確定,但后續狀態一定是確定了的,所以我們可以記憶化搜索這個題目,防止遍歷重復分支
可以用zx學長的簡單寫法,map套set來模擬上述過程,網上更普遍的是一種用二進制狀壓的寫法,因為最小的質數時2,而每個數最大只有1e9,即使是每次都除以2,最多也只需要除32次,所以用一個int類型的變量狀壓即可,第一位代表著1,第二位代表著和2,以此類推,我是用的狀態壓縮
還有一個小細節。。我也不知道為什么素數表打了個1e4會WA,后來改成1e5就A了,(其實正宗的唯一分解定理是不需要素數表的,直接從1枚舉到sqrt(n),時間復雜度也為O(sqrt(n)),但我為了省時間,預處理打了個素數表
對了,關于二進制情況下該怎么得到后續狀態,這個有點難理解,不過原理和上述的一模一樣,先掛位運算的公式:
(x>>i)|(x&(1<<i-1)-1)? ? (i為枚舉到了哪一位冪次,x為當前狀態)
稍微解釋一下這個公式吧,前面的(x>>i)是將大于等于k的數減去了k,后面(1<<i-1)-1是為了先讓小于k的那些位置上都變為1,然后再與原狀態取與運算,就能得到原位置上小于k的數有哪些了,最后讓原本小于k的數,和大于等于k的數減去了k之后的二進制,取一下或運算,就是新的狀態了
不得不佩服二進制和位運算的精妙之處,若是仍然不理解的話,完全可以用map套set來寫,那個更好理解更好實現,就是在得到后續狀態的時候需要自己手動模擬上述證明的過程,區別不算很大
直接掛代碼吧:
#include<iostream> #include<cstdlib> #include<string> #include<cstring> #include<cstdio> #include<algorithm> #include<climits> #include<cmath> #include<cctype> #include<stack> #include<queue> #include<list> #include<vector> #include<set> #include<map> #include<sstream> #include<unordered_map> using namespace std;typedef long long LL;const int inf=0x3f3f3f3f;const int N=1e5+100;int pri[N];bool vis[N];int cnt;unordered_map<int,int>sg,mp;void P()//歐拉線性篩 {for(int i=2;i<N;i++){if(!vis[i])pri[cnt++]=i;for(int j=0;j<cnt&&pri[j]*i<N;j++){vis[pri[j]*i]=true;if(i%pri[j]==0)break;}} }void solve(int num)//唯一分解定理 {for(int i=0;i<cnt&&pri[i]*pri[i]<=num;i++){int sum=0;while(num%pri[i]==0){sum++;num/=pri[i];}if(sum)mp[pri[i]]|=1<<(sum-1);if(num==1)break;}if(num>1)mp[num]|=1; }int getpos(int x)//得到最高位的位置 {int ans=0;while(x){x>>=1;ans++;}return ans; }int get_sg(int x)//記憶化搜索計算sg函數 {if(sg[x])return sg[x];if(x==0)return 0;map<int,bool>mex;int pos=getpos(x);for(int i=1;i<=pos;i++)mex[get_sg((x>>i)|(x&(1<<i-1)-1))]=true;for(int i=0;;i++)if(!mex[i])return sg[x]=i; }int main() { // freopen("input.txt","r",stdin);P();int n;scanf("%d",&n);for(int i=1;i<=n;i++){int num;scanf("%d",&num);solve(num);}int ans=0;for(auto it=mp.begin();it!=mp.end();it++)ans^=get_sg(it->second);if(ans)printf("Mojtaba\n");elseprintf("Arpa\n");return 0; }?
總結
以上是生活随笔為你收集整理的CodeForces - 850C Arpa and a game with Mojtaba(博弈+sg函数)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CodeForces - 603C Li
- 下一篇: CodeForces - 1110G T