1048 游戏 sg函数变式 博弈论
链接:https://ac.nowcoder.com/acm/contest/26656/1048
来源:牛客网
题目描述
小N和小O在玩游戏。他们面前放了n堆石子,第i堆石子一开始有ci颗石头。他们轮流从某堆石子中取石子,不能不取。最后无法操作的人就输了这个游戏。但他们觉得这样玩太无聊了,更新了一下规则。具体是这样的:对于一堆有恰好m颗石子的石头堆,假如一个人要从这堆石子中取石子,设他要取石子数为d,那么d必须是m的约数。最后还是无法操作者输。
现在小N先手。他想知道他第一步有多少种不同的必胜策略。一个策略指的是,从哪堆石子中,取走多少颗石子。只要取的那一堆不同,或取的数目不同,都算不同的策略。
现在小N先手。他想知道他第一步有多少种不同的必胜策略。一个策略指的是,从哪堆石子中,取走多少颗石子。只要取的那一堆不同,或取的数目不同,都算不同的策略。
输入描述:
第一行一个整数n。5
接下来一行n个整数,分别代表每堆石子的石子数目。
数据保证输入的所有数字都不超过10
,均大于等于1,且为整数。
输出描述:
一行一个整数代表小$N$第一步必胜策略的数量。
分析
标准的集合类nim游戏问题。
普通的集合类nim游戏:有几种取法,在n堆石子里取,问最终结果。将每一堆石子的sg值算出来就是答案。
这题:每堆石子可以取自己的因子的数量的石子,从n堆取,问最终结果。要将每一堆石子的sg值算出来就很复杂,需要用到之前的状态,而且还要求出有多少种不同取法。
问题1:算出每堆石子的sg值
由于石子数目小于一万,所以可以从小到大dp,预处理出所有石子的sg值,当求后面的石子的sg值的时候,前面所有石子的sg值已经求过了
设f[i] 表示 i 个石子的sg值
状态转移:设i 的因子d,f[i] = mex(f[i-d1],f[i-d2],f[i-d3]....)
问题2:通过算出的sg值,判断第一步有多少取法
假设已经求出了所有sg值的异或和 sum
要获得胜利,就要让对方变成必败状态,即sg值为0的状态。
让sum 异或 每堆石子数目。消去当前堆石子的总数的影响。
遍历当前堆石子的所有因子,如果异或这个因子能够使sum 值为0,即为必败状态。方案数 + 1
//-------------------------代码---------------------------- //#define int ll const int mod = 1e9 +7,md = 1e9+6,N = 1e5+10; int a[N]; int sg[N]; vector<int>G[N]; int vis[N]; void solve() { int cnt = 0; for(int i = 1;i<N;i++) { cnt ++ ; int k = sqrt(i); for(int j = 1;j<=k;j++) { if(i % j == 0) { vis[sg[i-j]]=cnt; if(j*j!=i) vis[sg[i-i/j]]=cnt; } } for(int j = 0;j<=N-10;j++) if(vis[j] != cnt) { sg[i] = j; break; } } int n;cin>>n; int sum = 0; for(int i = 1;i<=n;i++) { cin>>a[i],sum ^= sg[a[i]]; } int ans = 0; for(int i = 1;i<=n;i++) { sum ^= sg[a[i]]; int k = sqrt(a[i]); for(int j = 1;j<=k;j++) { if(a[i] % j == 0) { if((sg[a[i] - j]^sum) == 0) {ans ++ ;} if(j * j != a[i] && ( sg[a[i] - a[i] / j] ^ sum) == 0) ans ++ ; } } sum ^= sg[a[i]]; } cout<<ans<<endl; } signed main(){ clapping();TLE; // int t;cin>>t;while(t -- ) solve(); // {solve(); } return 0; } /*样例区 */ //------------------------------------------------------------