【2012 Semifinal 1】 YetAnotherNim

来源:互联网 发布:java循环#拼接字符串 编辑:程序博客网 时间:2024/05/20 21:05

Description

现在有一个博弈游戏。
有n堆石子,每堆石子的数量在  之间,其中 .
先手先从中选出连续K堆石子,删掉其他的所有堆。
后手接着删去任意堆石子,可以不删,但是不能全删。
然后两人开始玩NIM游戏。
求后手必胜的初始局面数量。

Difficulty

★★★

Main Algorithm

DP 矩乘加速
线性代数
博弈论

Complexity

Solution

挺有意思的。
我们发现关键是后手删去任意堆石子后,剩下的石子异或和为0.
即先手取了任意连续K堆石子后,都存在一组数使得异或和为0.
那么就继续用线性代数来描述。题目变为求长度为n,每个数均在1~m之间,任意连续K个数线性相关的序列个数。
显然的当  的时候,任意K个数必然是线性相关的,那么答案就是 
当  的时候,直接做似乎不好做,线性相关这个东西看上去并不容易压入状态。
那么补集转化为求存在某K个数线性无关的序列个数。
这样,对线性无关的讨论似乎更方便,因为线性无关组中一个数所控制的位仅一位。
设  表示考虑了前i个数,且恰好后j个数线性无关的序列个数。
考虑新加入一个数。
假如这个数与后j个数都线性无关,那么除了只在这j位为1的数之外,都可以选。
.
假如这个数与后v个数线性无关,但是与后v+1个数线性相关:
那么这个数在从后数第v+1个数控制的位上是1,除了后v+1个数控制的位,别的都不能为1(若在除了这v+1位上有1,则会导致这个数对于后v+1个数线性无关)。
.
而  不能往别的状态上转移了,但i后面的数就能任取了。其对答案的贡献为 .
考虑用矩阵乘法加速。
前面的那一大堆的系数都是常数。唯有向答案贡献的地方乘的是 。怎么办呢?考虑最后的答案是个关于m的多项式,每个系数就是 ,那么用秦九韶算法展开后,所有系数便都变为常数了。


#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>#include <string>#include <cmath>#define Rep(i, x, y) for (int i = x; i <= y; i ++)#define Dwn(i, x, y) for (int i = x; i >= y; i --)#define RepE(i, x) for(int i = pos[x]; i; i = g[i].nex)using namespace std;typedef long long LL;const int N = 105, mod = 1000000007;int u = 1, u1, n; LL A0, s2[N];struct Matr {LL a[N][N];Matr() { memset(a, 0, sizeof(a)); }} p, q;Matr operator* (Matr x, Matr y) {Matr z;Rep(i, 1, n)Rep(k, 1, n)Rep(j, 1, n) (z.a[i][j] += x.a[i][k] * y.a[k][j]) %= mod;return z;}class YetAnotherNim {public:void Pow(int x) {while (x) {if (x & 1) q = q * p;p = p * p, x >>= 1;}}int pow2(LL x, int y) {LL z = 1;while (y) {if (y & 1) (z *= x) %= mod;y >>= 1, (x *= x) %= mod;}return z;}int solve(int T, int m, int n0) {n = n0, A0 = pow2(m, T);while (u <= m) u *= 2, u1 ++;if (n == 1) return 0;if (n > u1) return A0;// puts("fin");s2[0] = 1;Rep(i, 1, n - 1) {s2[i] = s2[i - 1] * 2 % mod;Rep(j, 1, i) p.a[i][j] = s2[j - 1];p.a[i][i + 1] = (m + 1 - s2[i] + mod) % mod;} p.a[n][n] = m;q.a[1][1] = m;Pow(T - 1);//cout << A0 << endl;return int((A0 - q.a[1][n] + mod) % mod);}};


0 0
原创粉丝点击