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


标题:约数倍数选卡片

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

在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)//check(x)x取掉之后下一个人的先手状态
29 {//return 1的话,当前做决定的人必败,所以拿掉x那个是必胜的
30 //return 0的话,当前做决定的人必胜,所以拿掉x那个是必败的
31     for(int i=head[x],j;~i;i=S[i].ne)
32     {
33         j=S[i].v;
34         if(num[j])
35         {
36             num[j]--;
37             if(check(j))//取完j后是必胜态的话,
38             {//对于当前取完x状态来说就是必败态 
39                 num[j]++;
40                 return 0; 
41             }
42             num[j]++;
43         }
44     }
45     return 1;//当前状态不能再选时,上一个就是必胜态
46 }
47 int main()
48 {
49     init();
50     int x;
51     string s;
52     getline(cin,s);
53     stringstream sina(s);
54     while(sina>>x)//string流读入 
55         num[x]++;//统计每个数的个数
56     //前向星建图是头插法,所以从小到大建 
57     for(int i=1;i<=100;i++)
58     {
59         if(num[i])
60         {
61             for(int j=i;j<=100;j++)
62                 if(num[j]&&(i%j==0||j%i==0))
63                 {//如果i和j都存在,而且彼此是约数或者倍数就建边 
64                     add(i,j);
65                     if(i!=j)//避免自己到自己建两条边 
66                         add(j,i);
67                 }
68         }
69     }
70     getline(cin,s);
71     stringstream sinb(s);
72     while(sinb>>x)
73         v.push_back(x);
74     sort(v.begin(),v.end());
75     int ans=-1;
76     for(int i=0;i<v.size();i++)
77     {
78         if(i&&v[i]==v[i-1])//去重 
79             continue;    
80         num[v[i]]--;//从剩余卡牌中去掉一张当前的数的卡牌 
81         if(check(v[i]))//dfs爆搜这种情况的状态 
82         {
83             ans=v[i];
84             break;
85         }
86         num[v[i]]++;
87     }
88     printf("%d\n",ans);
89     return 0;
90 }
必胜必败

转载于:https://www.cnblogs.com/LMCC1108/p/10896950.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值