蓝桥历年套题 约数倍数选卡片 博弈


标题:约数倍数选卡片

闲暇时,福尔摩斯和华生玩一个游戏:

在N张卡片上写有N个整数。两人轮流拿走一张卡片。要求下一个人拿的数字一定是前一个人拿的数字的约数或倍数。例如,某次福尔摩斯拿走的卡片上写着数字“6”,则接下来华生可以拿的数字包括:

1,2,3, 6,12,18,24 ....

当轮到某一方拿卡片时,没有满足要求的卡片可选,则该方为输方。

请你利用计算机的优势计算一下,在已知所有卡片上的数字和可选哪些数字的条件下,怎样选择才能保证必胜!

当选多个数字都可以必胜时,输出其中最小的数字。如果无论如何都会输,则输出-1。


输入数据为2行。第一行是若干空格分开的整数(每个整数介于1~100间),表示当前剩余的所有卡片。
第二行也是若干空格分开的整数,表示可以选的数字。当然,第二行的数字必须完全包含在第一行的数字中。

程序则输出必胜的招法!!


例如:
用户输入:
2 3 6
3 6
则程序应该输出:
3

再如:
用户输入:
1 2 2 3 3 4 5
3 4 5
则程序应该输出:
4

  一开始感觉没啥思路,肯定是数论,但sg表是打不了,然后看了下数据1~100,模拟了下就感觉可以直接暴力跑状态。

  首先要知道,必胜态和必败态的转换,首先如果一个状态先手能走到一个必败态的话那么它就是必胜态,反之,如果一个状态能走到的状态全是必胜态的话那么它就是必败态。

  最终的必败态肯定就是不能再选时,所以我们直接就是根据状态的转换,暴力深搜模拟所有可能的情况,得到每一步的状态,然后推出一开始的状态是必胜还是必败。思路就是这样,很简单,但很容易超时,有两个解决超时的优化,第一个是在读入上用sting流读入,这个c++课一开始就学了,不懂的可以去了解一下。第二个就是在每个数下一步可以走到的数的处理上,我们类似建图一样建边,然后让大的数在前面,这样每次走的时候先走大的数。(这里我也不知道为什么,我一开始从小到大就TLE了),剩下的就是详情见代码了。

 1 #include<cstdio>
 2 #include<sstream>
 3 #include<iostream>
 4 #include<vector>
 5 #include<algorithm>
 6 using namespace std;
 7 const int N=118;
 8 struct Side{
 9     int v,ne;
10 }S[N<<2];
11 int sn,num[N],head[N];
12 vector<int> v;
13 void init()
14 {
15     sn=0;
16     for(int i=1;i<=100;i++)
17     {
18         head[i]=-1;
19         num[i]=0;
20     }
21 }
22 void add(int u,int v)
23 {
24     S[sn].v=v;
25     S[sn].ne=head[u];
26     head[u]=sn++;
27 }
28 bool check(int x)//1先手必胜 0先手必败 
29 {
30     for(int i=head[x],j;~i;i=S[i].ne)
31     {
32         j=S[i].v;
33         if(num[j])
34         {
35             num[j]--;
36             if(check(j))//如果走到了一个必胜态 
37             {
38                 num[j]++;
39                 return 0;//当前状态就是必败态 
40             }
41             num[j]++;
42         }
43     }
44     return 1;//不能再选时,当前状态就是必胜态
45 }
46 int main()
47 {
48     init();
49     int x;
50     string s;
51     getline(cin,s);
52     stringstream sina(s);
53     while(sina>>x)//string流读入 
54         num[x]++;//统计每个数的个数
55     //前向星建图是头插法,所以从小到大建 
56     for(int i=1;i<=100;i++)
57     {
58         if(num[i])
59         {
60             for(int j=i;j<=100;j++)
61                 if(num[j]&&(i%j==0||j%i==0))
62                 {//如果i和j都存在,而且彼此是约数或者倍数就建边 
63                     add(i,j);
64                     if(i!=j)//避免自己到自己建两条边 
65                         add(j,i);
66                 }
67         }
68     }
69     getline(cin,s);
70     stringstream sinb(s);
71     while(sinb>>x)
72         v.push_back(x);
73     sort(v.begin(),v.end());
74     int ans=-1;
75     for(int i=0;i<v.size();i++)
76     {
77         if(i&&v[i]==v[i-1])//去重 
78             continue;    
79         num[v[i]]--;//从剩余卡牌中去掉一张当前的数的卡牌 
80         if(check(v[i]))//dfs爆搜这种情况的状态 
81         {
82             ans=v[i];
83             break;
84         }
85         num[v[i]]++;
86     }
87     printf("%d\n",ans);
88     return 0;
89 }
必胜必败

猜你喜欢

转载自www.cnblogs.com/LMCC1108/p/10896950.html