标题:约数倍数选卡片
闲暇时,福尔摩斯和华生玩一个游戏:
在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
资源约定:
峰值内存消耗 < 64M
改良:
闲暇时,福尔摩斯和华生玩一个游戏:
在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
资源约定:
峰值内存消耗 < 64M
CPU消耗 < 1000ms
博弈论的理论就是,对手的必败态就是我的必胜态。需要注意的是只要某个选择可以导致对手出现必败态,则这个选择就是正确的,可以直接返回;而当我所有选择走了一遍之后,发现并没有返回,即并没有对手的必败态,才可以说明这是我的必败态。
开始只过了40%,原因是冗余变量多,最主要的问题就是在选择某一张卡片改变当前局面的时候,又重新选择了一遍它的约数倍数,而由于使用的是数组,所以就是按照整个规模即n来跑的循环,超时。
看了某神的代码后- - 嗯。可以通过vector事先将所有在范围内的数的约数倍数都存好,当选择了某一张卡片时,直接取到它对应的vector即可。而且数组也不用开那么多,最初的初始范围数组根本不必要,只需要num数组统计每一个数出现的次数即可。我开始的思想是从1到n遍历一遍,还要判断这个i出没出现过,还要判断它出现次数。。。然后选择了某个i还要重新赋值它的约数倍数。。。冗余变量和冗余循环都很多。精简以后可以每次只从可能的choice里边选,选择了以后直接改变它的出现次数变量,然后递归循环时新的可选择数也可以通过事先保存的vector取出。
tle:
#include <stdio.h>
#include <string.h>
int a[105], num[105], n, f = 0, ans;
int dfs(int sn, int flag[])
{
int i, j, tsn = 0, choose, f1[105];
if(sn == 0)
return -1;
for(i = 0 ; i < n ; i++)//全部卡片
{
if(a[i] != 0 && flag[a[i]])//没有被选走,并且还在可选择的范围内
{
choose = a[i];
a[i] = 0;//被选走了
for(j = 0 ; j < n ; j++)//修改目前可选择的
{
f1[a[j]] = 0;
if(a[j] != 0 && (a[j] % choose == 0 || choose % a[j] == 0))
{
f1[a[j]]++;
tsn++;
}
}
int temp = dfs(tsn, f1);
a[i] = choose;//开始时这个语句放的位置不对,放在了if之后,这样的话如果返回了一个必败局面,则整个a数组都给改了
if(temp == -1)
{
ans = choose;
return 1;
}
}
}
return -1;
}
int main()
{
char c;
int flag[105];
int i = 0, j, sum = 0, x;
memset(flag, 0, sizeof(flag));
while(1)
{
scanf("%d", &a[i]);
num[a[i]]++;
i++;
c = getchar();
if(c == '\n')
break;
}
while(1)
{
scanf("%d", &x);
sum++;
flag[x]++;
c = getchar();
if(c == '\n')
break;
}
n = i;
int temp = dfs(sum, flag);
if(temp == 1)
printf("%d\n", ans);
else
printf("-1\n");
return 0;
}
改良:
#include <stdio.h>
#include <string.h>
#include <vector>
#include <algorithm>
using namespace std;
vector <int> choice;
vector <int> table[105];
int a[105], num[105], n, f = 0, ans;
int dfs(int choose)
{
int i, new_choose, t;
//if(sn == 0)可以去掉了,因为如果当前的choose已经没有可以选择的约数了,下边这个循环是不会进行的,也会走到返回-1那一步
// return -1;
for(i = table[choose].size() - 1 ; i >= 0 ; i--)
{
new_choose = table[choose][i];
if(num[new_choose])
{
num[new_choose]--;
t = dfs(new_choose);
num[new_choose]++;
if(t == -1)
return 1;
}
}
return -1;
}
int main()
{
char c;
int i = 0, j, sum = 0, x, new_choose, t;
while(1)
{
scanf("%d", &x);
num[x]++;//出现次数
i++;
c = getchar();
if(c == '\n')
break;
}
while(1)
{
scanf("%d", &x);
sum++;
choice.push_back(x);
c = getchar();
if(c == '\n')
break;
}
sort(choice.begin(), choice.end());
for(i = 1 ; i < 101 ; i++)//把每个数的约数倍数都事先存下来,要注意这里是倒序的
{
if(num[i])
{
for(j = 1 ; j < 101 ; j++)
{
if(num[j] && (i % j == 0 || j % i == 0))
table[i].push_back(j);
}
}
}
for(i = 0 ; i < choice.size() ; i++)
{
new_choose = choice[i];
num[new_choose]--;
t = dfs(new_choose);
num[new_choose]++;
if(t == -1)
{
printf("%d\n", new_choose);
return 0;
}
}
printf("-1\n");
return 0;
}