【2012 Semifinal 1】 YetAnotherNim

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


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值