约数倍数选卡片 博弈论

标题:约数倍数选卡片

    闲暇时,福尔摩斯和华生玩一个游戏:
    
    在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 
#include 
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 
#include 
#include 
#include 
using namespace std;
vector  choice;
vector  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;
}


你可能感兴趣的:(搜索,蓝桥,博弈)