首先先说两个定义 N状态:<font color=red>前面的一个玩家</font>必胜 P状态: <font color=red>后面一个玩家</font>必胜c++
有一堆数量为n的物体,轮流拿,至少拿1个,至多拿k个(N>K); 若是n%(k+1)==0
,那么先手必败。 <s>这一切是显而易见,毫无疑问的</s>ide
一道例题 Tang and Jiang are good friends. To decide whose treat it is for dinner, they are playing a game. Specifically, Tang and Jiang will alternatively write numbers (integers) on a white board. Tang writes first, then Jiang, then again Tang, etc... Moreover, assuming that the number written in the previous round is X, the next person who plays should write a number Y such that 1 <= Y - X <= k. The person who writes a number no smaller than N first will lose the game. Note that in the first round, Tang can write a number only within range [1, k] (both inclusive). You can assume that Tang and Jiang will always be playing optimally, as they are both very smart students.函数
这一道题目大意上和Bush Game 差很少,可是不一样的是它里面说的是写不出不小于n的就输了,那么必胜的时候就是你已经写出了n-1的时候。因此此时若是(n-1)%(k+1)==0
那么先手一定输,不然就是后手输,由于先手那一次后可已转换为第一种状况spa
有n堆物体,每一堆的数量为a[i]个,每一次一我的任选一堆取出任意个(不能为0) 设k为每一堆异或的结果,若是k==0
,那么先手一定失败,不然先手一定胜利。 这样来想,把每一堆的数量转换成二进制而后竖着来最低位对齐。若是说k==0
,那么这意味这每个列的1的个数必定会是偶数个。而第一我的取走了一些之后呢,这意味着必定一些列的1的个数为变成奇数个,那么k必定就再也不等于0了。而此时后手的人只要把异或的值修正为0就能够了。 由于<font color=red>k不等于0了,那么这就说明k的最高位必定会是1,而这个最高位的1必定会是a[i]中的对应位上的1提供的</font>。 而根据异或的性质,k和a[i]异或结果为其余的异或结果。 咱们能够在a[i]中减去一些值,是a[i]最终等于k和a[i]异或的结果,<font color=red>又由于k最高位的1是由a[i]提供的,那么那里必定会变成0,就必定会比a[i]小,而只要把这里减掉,k就又会等于0了</font>code
1.双方交替来进行; 2.游戏进行的任意时刻,能够执行的合法行动与哪个玩家执行无关; 3.当玩家没法行动的时候,就判负排序
$$ mex(S)=min{x|x\in N,x\notin S} $$队列
对于任意状态下的x $$ SG(x)=mex{SG(y)|y是x的后继状态} $$ 对于终止状态,SG值为0。游戏
若是某一状态后继SG有0,则当前状态为N 若是当前状态全部后继SG不为0,则当前为P;ip
好的,既然已经知道了SG函数是什么,那么就有一道题了。ci
<font color=blue>Description</font>
给定一个有N个节点的DAG图,图上某些节点上面有棋子。两名玩家交替移动棋子,玩家每一次能够将任意一颗棋子沿着有向边移动到下一个节点,当没法移动的时候,就输掉了游戏,假设双方都足够聪明,问先手必胜仍是后手必胜。
<font color=blue>Input</font>
第一行三个整数N,M,K,表示N个节点M条边K个棋子。接下来M行,每行两个整数x,y,表明x节点到y节点的有向边再接下来K行,表示K个棋子所在的节点编号。
<font color=blue>output</font>
先手胜输出"win",不然输出"lose"
<font color=blue>Sample Input</font>
6 8 4 2 1 2 4 1 4 1 5 4 5 1 3 3 5 3 6 1 2 4 6
<font color=blue>Sample Output</font>
win
这应该是我第一次这么抄题吧。 DAG图,看到这个应该回想起拓扑排序,其次,由于没有后继的点SG值为0,而每个点的SG值又是有它的后继决定的。若是它的后继没有弄完,那么就不能够算。这很像拓扑排序把入度为0的点push进队列。 所以不难想到这一题能够用相似于拓扑排序的方法来作。咱们须要存两个图,正向的和反向的。正向的图用来寻找这一个点的后继获取这一个点的SG值,反向的用来找祖宗,修改祖宗的出度,并把出度为0的祖宗push进入队列 由于有拓扑排序的基础,因此这一道题能够比较容易的作出来了
#include<bits/stdc++.h> #define maxn 2003 using namespace std; queue<int>Q; vector<int>G[maxn];//正着存图 vector<int>G_[maxn];//反着存图 int chess[maxn],SG[maxn];//记录棋子的位置 ,SG函数值 bool vis[maxn*3]; int n,m,outdgr[maxn];//记录点,边,出度 int k; void solve(){ for(int i=1;i<=n;i++){ if(!outdgr[i]){ Q.push(i); } } while(!Q.empty()){ memset(vis,0,sizeof(vis)); int u=Q.front();Q.pop(); vector<int>::iterator iter=G[u].begin(); int maxsg=0; while(iter!=G[u].end()){ maxsg=max(maxsg,SG[*iter]); vis[SG[*iter]]=true; iter++; } int j; for(j=0;j<=maxsg+1;j++)if(!vis[j])break; SG[u]=j;//获得这一个点的SG值 vector<int>::iterator iter_=G_[u].begin(); while(iter_!=G_[u].end()){ outdgr[*iter_]--; if(!outdgr[*iter_])Q.push(*iter_); iter_++; } } int ans=SG[chess[1]]; for(int i=2;i<=k;i++)ans^=SG[chess[i]]; if(!ans)printf("lose\n"); else printf("win\n"); return ; } int main(){ memset(outdgr,0,sizeof(outdgr)); scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=m;i++){ int u,v; scanf("%d%d",&u,&v); G[u].push_back(v); G_[v].push_back(u); outdgr[u]++; } for(int i=1;i<=k;i++){ scanf("%d",&chess[i]); } solve(); return 0; }
那么,That's all.