博弈论题目总结(二)——SG组合游戏及变形
SG函数
为了更一般化博弈问题,我们引入SG函数
SG函数有如下性质:
1.如果某个状态SG函数值为0,则它后继的每个状态SG函数值都不为0
2.如果某个状态SG函数值不为0,则它至少存在一个后继的状态SG函数值为0
如果某个局面SG函数值为0,则该局面先手必败
放到有向图中,该有向图的核就是SG值为0的点构成的集合
游戏的和
游戏的和的SG函数值=所有子游戏SG函数值的异或和Xor
如果所有子游戏都进行完毕,那么Xor=0,必败
如果某个状态的SG函数值为0,那么后手一定可以做出一种动作,保持Xor=0,那么先手必败。
反之某个状态的SG函数值不为0,先手可以让Xor=0,变成后手,重复上述动作,那么先手必胜
这样就能轻松合并多个独立的组合游戏啦
mex函数
$sg[$当前局面$]=mex(sg[$后继局面$])$
$mex$函数表示第一次还没出现的数
某种后继局面可能是很多个子游戏,那么该后继局面的$sg$函数就是这些子游戏的和,即子游戏$sg$函数的异或和
SG组合游戏
NIM游戏
有n堆石子,两个人玩游戏,每次轮流在一堆里取走任意个,取走最后一堆的最后一个石子的人赢,问谁赢
一堆石子相当于一个子游戏,显然该子游戏的SG函数值为该堆中石子数
再根据游戏的和的思想,把子游戏合并就能求出谁赢了
NIMk游戏
同样是NIM游戏,现在变成了每次在k堆中取任意个
NIM游戏采取策略的根本是,保证当SG函数值为0时,不论先手如何操作,后手一定能做出一种动作,保持Xor=0
把每堆石子都转化成k+1进制数,再进行不进位的加法即可
SG组合游戏
POJ 2960 S-Nim (SG函数递推)
裸题,考察对SG函数的理解。利用游戏的和以及mex函数。暴力递推出石子SG函数即可
1 #include <cmath> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #define N1 10010 6 #define ll long long 7 #define ull unsigned long long 8 using namespace std; 9 10 const int inf=0x3f3f3f3f; 11 int gint() 12 { 13 int ret=0,fh=1;char c=getchar(); 14 while(c<'0'||c>'9'){if(c=='-')fh=-1;c=getchar();} 15 while(c>='0'&&c<='9'){ret=ret*10+c-'0';c=getchar();} 16 return ret*fh; 17 } 18 19 int n,m,o,T,A,B,de; 20 int s[N1],a[N1],sg[N1],use[N1]; 21 22 int main() 23 { 24 while(scanf("%d",&m)) { 25 26 if(m==0) break; 27 int i,j,ans=0,flag; 28 memset(sg,0,sizeof(sg)); 29 for(i=1;i<=m;i++) s[i]=gint(); 30 sg[0]=0; 31 for(i=1;i<=10000;i++) 32 { 33 for(j=1;j<=m;j++) if(i>=s[j]) use[sg[i-s[j]]]=1; 34 for(j=0;j<=m;j++) if(!use[j]){ sg[i]=j; break; } 35 for(j=1;j<=m;j++) if(i>=s[j]) use[sg[i-s[j]]]=0; 36 } 37 38 o=gint(); 39 while(o--) { 40 41 n=gint(); 42 for(i=1;i<=n;i++) a[i]=gint(); 43 for(i=1,ans=0;i<=n;i++) ans^=sg[a[i]]; 44 if(ans) putchar('W'); else putchar('L'); 45 46 } 47 puts(""); 48 49 } 50 return 0; 51 }
BZOJ 2940 条纹 (SG函数递推)
稍微复杂了一点,但也没什么好说的,暴力递推SG函数就行了
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #define il inline 5 #define N1 1010 6 using namespace std; 7 const int maxn=1000; 8 9 template <typename _T> void read(_T &ret) 10 { 11 ret=0; _T fh=1; char c=getchar(); 12 while(c<'0'||c>'9'){ if(c=='-') fh=-1; c=getchar(); } 13 while(c>='0'&&c<='9'){ ret=ret*10+c-'0'; c=getchar(); } 14 ret=ret*fh; 15 } 16 17 int T,n,A,B,C; 18 int use[N1],sg[N1]; 19 20 int main() 21 { 22 scanf("%d%d%d",&A,&B,&C); 23 if(A>B) swap(A,B); if(A>C) swap(A,C); if(B>C) swap(B,C); 24 int i,j,k; 25 for(i=A;i<=maxn;i++) 26 { 27 for(j=0;j+A<=i;j++) use[sg[j]^sg[i-j-A]]=1; 28 for(j=0;j+B<=i;j++) use[sg[j]^sg[i-j-B]]=1; 29 for(j=0;j+C<=i;j++) use[sg[j]^sg[i-j-C]]=1; 30 for(j=0;j<=maxn*maxn;j++) if(!use[j]){ sg[i]=j; break; } 31 for(j=0;j+A<=i;j++) use[sg[j]^sg[i-j-A]]=0; 32 for(j=0;j+B<=i;j++) use[sg[j]^sg[i-j-B]]=0; 33 for(j=0;j+C<=i;j++) use[sg[j]^sg[i-j-C]]=0; 34 } 35 scanf("%d",&T); 36 while(T--) 37 { 38 scanf("%d",&n); 39 if(sg[n]) puts("1"); else puts("2"); 40 //printf("%d\n",f[n]); 41 } 42 return 0; 43 } 44 45 /* 46 47 */
一个不错的模型转化
POJ 1704 Georgia and Bob (阶梯博弈)
题目大意:略
把相邻两个数的距离的差值-1看成一堆石子,第一堆石子数是第一个数到1的距离
那么把第i个数向左移x格相当于把x个石子从第i堆放到第i+1堆里,而挪第n堆的石子则是把石子移出游戏
游戏结束状态就是所有的石子都移出了游戏
发现从右往左数的偶数堆(第2,4,6..堆)的石子是无意义的,因为先手挪,后手就跟着挪,先手只会输
我们只考虑从右往左数的奇数堆,先手把石子从奇数堆移动到了偶数堆,相当于把这些石子移除游戏,也就是删除了奇数堆中的这些石子!
问题转化成了NIM游戏!我们只对从右往左数的奇数堆讨论即可
1 #include <cmath> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #define N1 10010 6 #define ll long long 7 #define ull unsigned long long 8 using namespace std; 9 10 const int inf=0x3f3f3f3f; 11 int gint() 12 { 13 int ret=0,fh=1;char c=getchar(); 14 while(c<'0'||c>'9'){if(c=='-')fh=-1;c=getchar();} 15 while(c>='0'&&c<='9'){ret=ret*10+c-'0';c=getchar();} 16 return ret*fh; 17 } 18 19 int n,m,T,A,B,de; 20 int a[N1],sg[N1]; 21 22 int main() 23 { 24 scanf("%d",&T); 25 while(T--){ 26 27 int i,j,ans=0; 28 scanf("%d",&n); 29 for(i=1;i<=n;i++) a[i]=gint(); 30 sort(a+1,a+n+1); 31 for(i=2;i<=n;i++) sg[n-i+1]=a[i]-a[i-1]-1; sg[n]=a[1]-1; 32 for(i=1;i<=n;i+=2) ans^=sg[i]; 33 if(!ans) puts("Bob will win"); 34 else puts("Georgia will win"); 35 36 } 37 return 0; 38 }
很多问题里状态很大,我们不能预处理出SG函数值,只能打表找规律
博弈问题的精髓是打表!
HDU 3032 Nim or not Nim (SG函数打表)
题目大意:NIM游戏,每次操作还可以把一堆石子分成两堆,问谁赢
先预处理出单独一堆石子时的sg函数值。
那么,该游戏的sg函数值=每堆石子的sg函数值的异或和
暴力枚举后继状态,打个表找规律就行了
1 #include <cmath> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #define N1 1000050 6 #define ll long long 7 #define dd double 8 using namespace std; 9 const dd eps=1e-7; 10 11 template <typename _T> void read(_T &ret) 12 { 13 ret=0; _T fh=1; char c=getchar(); 14 while(c<'0'||c>'9'){ if(c=='-') fh=-1; c=getchar(); } 15 while(c>='0'&&c<='9'){ ret=ret*10+c-'0'; c=getchar(); } 16 ret=ret*fh; 17 } 18 19 int T,n; 20 int a[N1],sg[N1],use[N1]; 21 const int maxn=10000; 22 23 int main() 24 { 25 scanf("%d",&T); 26 while(T--){ 27 28 scanf("%d",&n); 29 int i,j,sum=0; 30 for(i=1;i<=n;i++) 31 { 32 read(a[i]); 33 if(a[i]%4==3) sum^=a[i]+1; 34 else if(a[i]%4==0) sum^=a[i]-1; 35 else sum^=a[i]; 36 } 37 if(!sum) puts("Bob"); else puts("Alice"); 38 39 } 40 return 0; 41 } 42 43 /* 44 scanf("%d",&n); 45 int i,j; 46 //for(i=1;i<=n;i++) scanf("%d",&a[i]); 47 sg[0]=0; sg[1]=1; 48 for(i=2;i<=n;i++) 49 { 50 use[sg[0]]=1; 51 for(j=1;j<i;j++) use[sg[j]]=1, use[sg[j]^sg[i-j]]=1; 52 for(j=0;j<=i*2;j++) if(!use[j]){ sg[i]=j; break; } 53 use[sg[0]]=0; 54 for(j=1;j<i;j++) use[sg[j]]=0, use[sg[j]^sg[i-j]]=0; 55 } 56 //for(i=0;i<=n;i++) printf("%d:%d\n",i,sg[i]); 57 for(i=1;i<=n;i++) printf("%d\n",sg[i]); 58 */
HDU 4644 Triangulation (SG函数打表)
题目大意:圆上有$a_{i}$个点,两个人玩游戏,轮流在这些点之间连边,边和边不能交叉,现在有n个圆,问谁赢
和上面的题一模一样的套路,每次连线都会产生两个子游戏,先写暴力打表,然后找规律
规律比较丧病
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #define il inline 5 #define N1 1000010 6 using namespace std; 7 8 template <typename _T> void read(_T &ret) 9 { 10 ret=0; _T fh=1; char c=getchar(); 11 while(c<'0'||c>'9'){ if(c=='-') fh=-1; c=getchar(); } 12 while(c>='0'&&c<='9'){ ret=ret*10+c-'0'; c=getchar(); } 13 ret=ret*fh; 14 } 15 16 int T,n; 17 int a[N1]; 18 int sg1[]={0,1,1,2,0,3,1,1,0,3,3,2,2,4,0,5,2,2,3,3,0,1,1,3,0,2,1,1,0,4,5,2,7,4,0,1,1,2,0,3,1,1,0,3,3,2,2,4,4,5,5,2}; 19 int sg2[]={3,3,0,1,1,3,0,2,1,1,0,4,5,3,7,4,8,1,1,2,0,3,1,1,0,3,3,2,2,4,4,5,5,9}; 20 21 int main() 22 { 23 scanf("%d",&T); 24 while(T--) { 25 26 scanf("%d",&n); 27 int i,j,sum=0; 28 for(i=1;i<=n;i++) 29 { 30 read(a[i]); 31 if(a[i]<=52) sum^=sg1[a[i]-1]; 32 else sum^=sg2[(a[i]-53)%34]; 33 } 34 if(sum) puts("Carol"); else puts("Dave"); 35 36 } 37 return 0; 38 } 39 40 /* 41 42 */
SG组合游戏变形
气氛变得怪异起来
一些SG组合游戏的终止状态比较特殊,我们可以通过转化解决
POJ 3537 Crosses and Crosses
题目大意:给出一个1*n的网格,一开始全都是白格子,两个人轮流把一个白格子涂黑,谁先涂出来连续3个黑格子谁就赢了
游戏的结束状态不容易直接搞啊
我们剖析游戏本身的性质
先手把一个格子涂黑后,它左右一共连续5个格子(边界另外讨论)一定不能被后手涂
相当于每次涂黑一个格子,删掉连续的不超过5个格子,两侧剩下的格子构成了一个或两个子游戏
依次求出长度为1~n的连续格子的游戏的SG函数即可
1 #include <queue> 2 #include <cmath> 3 #include <vector> 4 #include <cstdio> 5 #include <cstring> 6 #include <algorithm> 7 #define N1 2010 8 #define M1 200010 9 #define ll long long 10 #define dd double 11 using namespace std; 12 const dd eps=1e-7; 13 14 int n,tl; 15 int sg[N1],use[N1],que[N1]; 16 17 int main() 18 { 19 scanf("%d",&n); 20 if(n==1){ puts("1"); return 0; } 21 if(n==2){ puts("2"); return 0; } 22 if(n==3){ puts("1"); return 0; } 23 if(n==4){ puts("1"); return 0; } 24 sg[0]=0; sg[1]=sg[2]=sg[3]=1; sg[4]=2; 25 int i,j; 26 for(i=5;i<=n;i++) 27 { 28 que[++tl]=sg[i-4]; que[++tl]=sg[i-3]; 29 for(j=0;j+5<=i;j++) que[++tl]=sg[j]^sg[i-j-5]; 30 for(j=1;j<=tl;j++) use[que[j]]=1; 31 for(j=0;j<=i;j++) if(!use[j]){ sg[i]=j; break; } 32 while(tl) use[que[tl--]]=0; 33 } 34 if(sg[n]>0) puts("1"); 35 else puts("2"); 36 return 0; 37 }
POJ 2311 Cutting Game
题目大意:给出一张n*m的纸,每次可以把它剪成两半,先剪出来1*1小纸片的人赢
虽然1*1是必败局面,但并不容易直接推出其他格子的SG函数
显然x>1时,1*x的局面先手必胜。而2*2局面必败,进而可以推出2*3,3*3也都是必败局面
利用这两点就可以轻松推出整张纸的SG函数了
1 #include <queue> 2 #include <cmath> 3 #include <vector> 4 #include <cstdio> 5 #include <cstring> 6 #include <algorithm> 7 #define N1 205 8 #define M1 200010 9 #define ll long long 10 #define dd double 11 using namespace std; 12 const dd eps=1e-7; 13 14 int T,n,m,de; 15 int sg[N1][N1],use[N1*2]; 16 17 int main() 18 { 19 int i,j,k,x,y,ans,flag; 20 for(i=1;i<=200;i++) sg[1][i]=1; 21 for(i=1;i<=200;i++) sg[i][1]=1; 22 for(i=2;i<=200;i++) for(j=2;j<=200;j++) //if(i+j>4) 23 { 24 for(k=2;k<j-1;k++) use[sg[i][k]^sg[i][j-k]]=1; //if(i+k>=4&&i+j-k+1>=4) 25 for(k=2;k<i-1;k++) use[sg[k][j]^sg[i-k][j]]=1; //if(j+k>=4&&j+i-k+1>=4) 26 27 for(k=0;k<=200;k++) if(!use[k]){ sg[i][j]=k; break; } 28 29 for(k=2;k<j-1;k++) use[sg[i][k]^sg[i][j-k]]=0; //if(i+k>=4&&i+j-k+1>=4) 30 for(k=2;k<i-1;k++) use[sg[k][j]^sg[i-k][j]]=0; //if(j+k>=4&&j+i-k+1>=4) 31 } 32 while(scanf("%d%d",&n,&m)!=EOF) 33 { 34 if(sg[n][m]) puts("WIN"); 35 else puts("LOSE"); 36 } 37 return 0; 38 }
BZOJ 1457 棋盘游戏
题目大意:给出一个坐标系,上面有很多个皇后,皇后只能向左/向下/向左下走,两个人轮流每次选择一个皇后移动,谁先把皇后移动到(0,0)谁赢
如果直接把(0,0)当做游戏终止局面的话,求解的问题就是谁先把所有皇后都移动到(0,0)谁赢了
显然如果存在x=y||x=0||y=0的皇后,先手必胜
所以两个人都极力避免自已移动出来上述三种情况的皇后
而皇后只能向左下移动,最终皇后一定集中在(1,2)和(2,1)
我们把SG函数为0的位置设为(1,2)和(2,1)即可
1 #include <queue> 2 #include <cmath> 3 #include <vector> 4 #include <cstdio> 5 #include <cstring> 6 #include <algorithm> 7 #define N1 105 8 #define M1 200010 9 #define ll long long 10 #define dd double 11 using namespace std; 12 const dd eps=1e-7; 13 14 int T,n; 15 int sg[N1][N1],use[N1]; 16 17 int main() 18 { 19 int i,j,k,x,y,ans,flag; 20 for(i=1;i<=100;i++) for(j=1;j<=100;j++) if(i!=j) 21 { 22 for(k=1;k<j;k++) if(k!=i) use[sg[i][k]]=1; 23 for(k=1;k<i;k++) if(k!=j) use[sg[k][j]]=1; 24 for(k=1;k<min(i,j);k++) use[sg[i-k][j-k]]=1; 25 26 for(k=0;k<200;k++) if(!use[k]){ sg[i][j]=k; break; } 27 28 for(k=1;k<j;k++) if(k!=i) use[sg[i][k]]=0; 29 for(k=1;k<i;k++) if(k!=j) use[sg[k][j]]=0; 30 for(k=1;k<min(i,j);k++) use[sg[i-k][j-k]]=0; 31 } 32 scanf("%d",&T); 33 while(T--) 34 { 35 scanf("%d",&n); ans=0,flag=0; 36 for(i=1;i<=n;i++) 37 { 38 scanf("%d%d",&x,&y); 39 if(x==y||!x||!y) flag=1; 40 ans^=sg[x][y]; 41 } 42 if(ans||flag) puts("^o^"); 43 else puts("T_T"); 44 } 45 return 0; 46 }
还有一些更加奇怪的变形..
POJ 3480 John (anti-SG组合游戏)
题目大意:$n$堆石子的$NIM$游戏,改成取走最后一个石子的人输,问谁赢
先求出该游戏的$sg$函数
<1>$sg$函数为0,且每堆石子数量都是1,显然先手必胜
<2>$sg$函数为1,且每堆石子数量都是1,显然先手必败
那单堆石子数量>1的情况呢?
<3>$sg$函数为0,且存在一堆石子数量>1,先手必败
(1)先手取走了一个石子的石子堆,把$sg$函数变成1
显然后手一定能把$sg$函数还原成0
最后一定会还剩下至少2个"石子数量>1的堆",且此时$sg$函数值为0
不论先手怎么取,sg函数都会变得>1
$NIM$游戏具有对称性!
即对于一个$sg$函数值为0的局面而言,不论先手如何操作,后手都能把$sg$函数调成0
而这种情况下,一定存在石子数>1的堆,所以后手肯定能保持$sg$函数值为1!
最后会剩下一个石子被先手取走,后手赢
(2)先手取走了石子数量>1的石子堆中的任意数量个,把$sg$函数变成>1
此时一定还剩下"石子数量>1的堆",后手也能把$sg$函数调成1
先手如果取单个石子的堆,后手也跟着取,保持$sg$函数值为1即可
<4>$sg$函数为>1,且存在一堆石子数量>1,先手必胜
先手把局面调成<3>就能让对手必败了
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #define N1 5050 5 using namespace std; 6 7 int n,m,T; 8 9 template <typename _T> void read(_T &ret) 10 { 11 ret=0; _T fh=1; char c=getchar(); 12 while(c<'0'||c>'9'){ if(c=='-') fh=-1; c=getchar(); } 13 while(c>='0'&&c<='9'){ ret=ret*10+c-'0'; c=getchar(); } 14 ret=ret*fh; 15 } 16 17 int a[N1]; 18 19 int main() 20 { 21 scanf("%d",&T); 22 while(T--) 23 { 24 scanf("%d",&n); 25 int i,sum=0,ma=0; 26 for(i=1;i<=n;i++) read(a[i]), sum^=a[i], ma=max(ma,a[i]); 27 if(ma==1){ 28 if(sum) puts("Brother"); 29 else puts("John"); 30 }else{ 31 if(sum) puts("John"); 32 else puts("Brother"); 33 } 34 } 35 return 0; 36 }
HDU 3595 GG and MM (every-SG组合游戏)
题目大意:两个人玩游戏,每个子游戏有两堆石子,设石子较少那一堆数量为$x$,那么当前操作的人要在较多的那一堆中取走$kx$个,$k$是正整数且$kx$不能超过石子数量。两个人必须轮流对每一个能操作的子游戏进行操作,结束最后一个游戏的人获胜。
我们希望必胜的游戏一直保持下去,必败的局面早点结束
先处理出每种子游戏的$sg$函数
定义$step$函数表示先手令该游戏结束的最优步数,必胜局面取最大步数,必败局面取最小步数,可得
$step[u]=$
$0\;(sg[u]=0)$
$min(step[v])+1\;(sg[u]=0,sg[v]>0)$
$max(step[u])+1\;(sg[u]>0,sg[v]=0)$
那么先手必胜当且仅当单一游戏中最大的$step$为奇数
这里的$sg$函数的用途是分析局面何时结束,$sg$函数本身并不能决定胜负
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #define il inline 5 #define N1 1010 6 using namespace std; 7 const int maxn=1000; 8 const int inf=0x3f3f3f3f; 9 10 template <typename _T> void read(_T &ret) 11 { 12 ret=0; _T fh=1; char c=getchar(); 13 while(c<'0'||c>'9'){ if(c=='-') fh=-1; c=getchar(); } 14 while(c>='0'&&c<='9'){ ret=ret*10+c-'0'; c=getchar(); } 15 ret=ret*fh; 16 } 17 18 int T,n,A,B,C,de; 19 int use[N1*N1],sg[N1][N1],step[N1][N1]; 20 21 int main() 22 { 23 24 int i,j,k,w,ans,a,b; 25 for(i=1;i<=maxn;i++) 26 for(j=1;j<=i;j++) 27 { 28 if(i==3&&j==2) 29 de=1; 30 for(k=j;k<=i;k+=j) use[sg[max(i-k,j)][min(i-k,j)]]=1; 31 for(k=0;k<=maxn*maxn;k++) if(!use[k]){ sg[i][j]=k; break; } 32 if(!sg[i][j]) step[i][j]=inf; 33 for(k=j;k<=i;k+=j) 34 { 35 a=max(i-k,j), b=min(i-k,j); 36 if(!sg[i][j]&&sg[a][b]) { 37 step[i][j]=min(step[i][j],step[a][b]); 38 }else if(!sg[a][b]){ 39 step[i][j]=max(step[i][j],step[a][b]); 40 } 41 use[sg[a][b]]=0; 42 } 43 step[i][j]++; 44 } 45 46 while(scanf("%d",&n)!=EOF) { 47 48 ans=0; 49 for(i=1;i<=n;i++) 50 { 51 scanf("%d%d",&A,&B); 52 if(A<B) swap(A,B); 53 ans=max(ans,step[A][B]); 54 } 55 if(ans&1) puts("MM"); else puts("GG"); 56 57 } 58 return 0; 59 } 60 61 /* 62 63 */
BZOJ 1393 Knight (every-SG组合游戏+打表)
打表没商量,思路和上一题差不多
这道题也启示我们一个技巧,辅助函数(本题中的$step$函数)和$sg$函数之间可能存在某种神♂秘的关系,如果表本身的规律不够明显,尝试分类讨论打表
例如此题中,$sg$函数值为0的状态step函数很有规律。而$sg$值不为0的状态的$step$函数,可以根据$sg$函数值为0的后继状态推出来
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #define il inline 5 #define N1 200010 6 using namespace std; 7 const int maxn=1000; 8 const int inf=0x3f3f3f3f; 9 10 template <typename _T> void read(_T &ret) 11 { 12 ret=0; _T fh=1; char c=getchar(); 13 while(c<'0'||c>'9'){ if(c=='-') fh=-1; c=getchar(); } 14 while(c>='0'&&c<='9'){ ret=ret*10+c-'0'; c=getchar(); } 15 ret=ret*fh; 16 } 17 18 int m,n,de; 19 20 inline int check(int x,int y) 21 { 22 if(x==n||y==n) 23 { 24 if(x==n) swap(x,y); 25 if(n%4==0){ if(x==n) return 0; return 1; } 26 if(n%4==1){ if(x==n-1) return 1; return 0; } 27 if(n%4==2){ if(1<=x%4&&x%4<=2) return 0; return 1; } 28 if(n%4==3){ return 1; } 29 } 30 if( 1<=x%4 && x%4<=2 && 1<=y%4 && y%4<=2 ) 31 return 0; 32 return 1; 33 } 34 inline int query(int x,int y) 35 { 36 if(x==n||y==n) 37 { 38 if(x==n) swap(x,y); 39 if(n%4==0) 40 { 41 if(x<=3) return n/2-1; 42 if(x==n) return n/2-1+((x-1)/4)*2+1; 43 return n/2-1+(x/4)*2; 44 } 45 if(n%4==1) 46 { 47 if(x==n) return n-1; 48 if(x==n-1) return n-2; 49 return n/2+(x-1)/4*2; 50 } 51 if(n%4==2) 52 { 53 return n/2-1+(x-1)/2; 54 } 55 if(n%4==3) 56 { 57 return n/2+x/4*2; 58 } 59 } 60 if(!check(x,y)) return (x/4+y/4)*2; 61 int ans=0; 62 if(1<=x-2 && y+1<=n && !check(x-2,y+1)) ans=max(ans,query(x-2,y+1)+1); 63 if(1<=x-2 && y-1>=1 && !check(x-2,y-1)) ans=max(ans,query(x-2,y-1)+1); 64 if(1<=x-1 && 1<=y-2 && !check(x-1,y-2)) ans=max(ans,query(x-1,y-2)+1); 65 if(x+1<=n && 1<=y-2 && !check(x+1,y-2)) ans=max(ans,query(x+1,y-2)+1); 66 return ans; 67 } 68 int xx[N1],yy[N1],step[N1]; 69 void solve() 70 { 71 int i,j,x,y,ma,tmp; 72 for(j=1,ma=0;j<=m;j++) 73 { 74 scanf("%d%d",&xx[j],&yy[j]); 75 ma=max(ma,query(xx[j],yy[j])); 76 } 77 if(ma&1){ 78 79 puts("YES"); 80 for(j=1;j<=m;j++) 81 { 82 x=xx[j]; y=yy[j]; tmp=query(x,y); 83 if(1<=x-2 && y+1<=n) if(tmp==query(x-2,y+1)+1){ printf("%d %d\n",x-2,y+1); continue; } 84 if(1<=x-2 && y-1>=1) if(tmp==query(x-2,y-1)+1){ printf("%d %d\n",x-2,y-1); continue; } 85 if(1<=x-1 && 1<=y-2) if(tmp==query(x-1,y-2)+1){ printf("%d %d\n",x-1,y-2); continue; } 86 if(x+1<=n && 1<=y-2) if(tmp==query(x+1,y-2)+1){ printf("%d %d\n",x+1,y-2); continue; } 87 } 88 89 }else puts("NO"); 90 } 91 92 93 int main() 94 { 95 int i,j,k,w,ans,x,y; 96 scanf("%d%d",&m,&n); 97 //if(n<=100) S1::solve(); else S2::solve(); 98 solve(); 99 return 0; 100 }