您的位置:首页 > 其它

【组合博弈】【sg定理】[HNOI2014] bzoj3576 江南乐

2015-03-02 07:58 405 查看
题目点这里

……这道题的分块……太迷了…………看了一个晚上终于……想通了

当时考这道题的时候 窝博弈基本没入门 = = sg定理是个啥都不懂orz 然后去恶补看了几篇论文(虽然还是感觉他们写的太高深) 大概明白了一点点。。

其实这道题就是传说中的multi-nim游戏 orz

传统的nim游戏是有一堆石子 然后可以从每堆石子中拿走若干个 不可以不拿

结论是如果每堆石子数量异或和为0则先手必胜 反之先手必败

我们把每堆石子称为一个子游戏 也就是说每次操作可以对任意一个子游戏操作 当所有子游戏都完成了以后游戏结束

很明显对于nim游戏的每个子游戏 都是先手必胜的

引入SG值的概念

SG值:一个点的SG值就是一个不等于它的后继点的SG的且大于等于零的最小整数。

后继点:也就是按照题目要求的走法(比如取石子可以取的数量,方法)能够走一步达到的那个点。

不难发现 当且仅当SG(x) == 0时 x为必败状态。同时也可以证明 单堆的nim游戏每堆的SG值为它的石子数量

然后就是神奇的SG定理:主游戏的SG值等于所有子游戏的异或和

所以很容易证明上面的结论

然后我们来看multi-nim 其实就是每个子游戏可以分裂成多个子游戏

所以每个子游戏的SG值就为分裂出的子游戏的SG值的异或和

于是70分的暴力方法就出来了 枚举所有当前状态可以分裂出的状态 时间复杂度是O(n^2)的

int cal(int n)
{
vis.reset();
rep(m,2,n)
{
int x=0;
int a=m*(n/m+1)-n; // a表示n/m下取整
int b=m-a; // b表示n/m上取整
if(a&1) x^=f[n/m];
if(b&1) x^=f[n/m+1];
vis[x]=1;
}
int r=0;
for(;vis[r];r++);
return r;
}

然后分析可以发现 把n个石子分成i堆 当n / i == n / (i + 2)  (n / i 为分出来以后每堆的最小石子数)时sg值异或和结果相同

所以此时只需要将情况分为奇数堆与偶数堆(i为奇数或或偶数)两种 所以共有2*sqrt(n)种情况

具体的实现是这样的 先从2开始枚举i 然后算出last = x / (x / i) last表示满足n / i == n / j 的j的最大值 所以再下次的i就直接为last + 1即可

当且仅当last != i时 会出现两种以上的情况 既会出现n / i == n / j (i != j)

此时讨论i的奇偶性 由上面的结论知道计算sg(n / i) sg(n / i + 1) 后再计算sg(n / (i+1)) sg(n / (i+1) + 1) 即可

这样 所有可以转移到当前状态的sg值就都被求了出来 再求mex(S)就可以求出当前状态的sg值了

#include <cstdio>
#include <iostream>
#include <cstring>

using namespace std;

int read()
{
int sign = 1, n = 0; char c = getchar();
while(c < '0' || c > '9'){ if(c == '-') sign = -1; c = getchar(); }
while(c >= '0' && c <= '9') { n = n*10 + c-'0'; c = getchar(); }
return sign*n;
}

const int Nmax = 100005;

int T, F;
int N, id, sg[Nmax], vis[Nmax];
bool done[Nmax];

void get_sg(int x)
{
if(x < F)
{
sg[x] = 0;
return;
}
if(done[x]) return;
done[x] = 1;

for(int i = 2, last; i <= x; i = last + 1)
{
last = x / (x / i);
get_sg(x / i); get_sg(x / i + 1);
if(last > i)
{
++i; get_sg(x / i); get_sg(x / i + 1);
}

}
//cout << x << ":" << endl;
++id;
for(int i = 2, last; i <= x; i = last + 1)
{
last = x / (x / i);
//cout << "i:" << i << " last:" << last << " x / i:" << x / i << endl;
int temp = 0;
int b = x % i, a = i - b;
if(a & 1) temp ^= sg[x / i];
if(b & 1) temp ^= sg[x / i + 1];
vis[temp] = id;
if(last > i)
{
++i; last = x / (x / i);
//cout << " i:" << i << " last:" << last << " x / i:" << x / i << endl;
temp = 0;
b = x % i, a = i - b;
if(a & 1) temp ^= sg[x / i];
if(b & 1) temp ^= sg[x / i + 1];
vis[temp] = id;
}
}
//puts("");
for(sg[x] = 0; vis[sg[x]] == id; ++sg[x]);
}

int main()
{
T = read(); F = read();

while(T--)
{
N = read(); int ans = 0;

while(N--)
{
int x = read(); get_sg(x);
ans ^= sg[x];
}
printf("%d", ans ? 1 : 0); if(T) putchar(' ');
}
puts("");

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: