蓝桥历年套题 约数倍数选卡片 博弈
标题:约数倍数选卡片
闲暇时,福尔摩斯和华生玩一个游戏:
在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 }