博弈论题目总结(二)——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 }
View Code

 

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 */
View Code

 

 

一个不错的模型转化

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 }
View Code

 

 

 

很多问题里状态很大,我们不能预处理出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 */
View Code

 

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 */
View Code

 

 

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 }
View Code

 

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 }
View Code

 

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 }
View Code

 

 

还有一些更加奇怪的变形..

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 }
View Code

 

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 */
View Code

 

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 }
View Code

 

posted @ 2019-02-27 23:32  guapisolo  阅读(777)  评论(0编辑  收藏  举报