http://acm.sgu.ru/problem.php?contest=0&problem=153
题意:有N根火柴,m个数p1,p2,p3,..pm(m<=8,pi<=9),每个人可以轮流取1根或者pi根火柴,问先取的还是后取的有必胜策略。
解析:这个题有点类似与取石子游戏,我们不记f[n]为n根火柴时先取的是否有必胜策略(1或0).那么f[i]是只与f[i-1], f[i-p[1]], f[i-p[2]], ... f[i-p[m]]有关的,如果这些状态都是赢,那么f[i]就是输,否则f[i]是赢.
但是n太大了10^9。注意到pi最大只有9,那么pi必定是由i前9之内的某个或某些状态推出来的,由于九个状态的组合只有2^9 = 512 种,如果找到2个相等的话就找到了周期,那么f必存在一个小于512的周期。
所以问题转化为了找f的周期,求值.
code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int main()
{
//freopen("input.txt","r",stdin);
int k;
scanf("%d",&k);
int p[100];
int f[512];
int pos[512];
while(k--)
{
memset(pos,0,sizeof(pos));
int m,n;
scanf("%d%d",&n,&m);
for(int i=0;i<m;i++)
scanf("%d",&p[i]);
p[m++] = 1;
sort(p,p+m);
int t = 0;
for(int i=0;i<m;i++)
if(p[i]!=p[i+1] || i == m-1)p[t++] = p[i];
m = t;
f[1] = 0;
int l2 = 2,l1 = -1;
int x = 0;
while(l1 == -1)
{
f[l2] = 1;
for(int i=0;i<m && l2-p[i] > 0 && f[l2];i++) f[l2] &= f[l2-p[i]];
f[l2] = !f[l2];
if(pos[x]) l1 = pos[x];
else
{
pos[x] = l2;
x = (x<<1) & 0x1ff | f[l2];
l2++;
}
}
int ans;
if(n<=l1) ans = f[n];
else ans = f[(n-l1)%(l2-l1)+l1];
if(ans)printf("FIRST PLAYER MUST WIN\n");
else printf("SECOND PLAYER MUST WIN\n");
}
return 0;
}