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);
}
};