标签:style http color io os ar strong for 数据
题目链接
http://poj.org/problem?id=1704
这个题目是个好题,没有两下子是做不出的,其中考到,要你排序,如何把题目化成我们熟知的东西,
在这个题中我开始用选择法排序,他给我个wang answer,我表示不理解,但用sort AC了,谁知道可以告诉我一声,表示不甚感激!
题目的思路(来自网上)很遗憾自己开始连题目的看不懂,思路更不用说了。
这题是暑假ACM集训时做的,是尼姆博弈的变形运用,最初做这题时,把任意两棋子之间的距离当做了尼姆博弈中的一堆石子(或纸牌)。这想法太天真了,经高手指点后,发现要把棋子配对。现在假设处于这种情况——每两个棋子都是紧挨着的。这种状态是种必败的状态,无论你移动哪个棋子,下个玩家会移动另一个棋子,使他们再次紧挨着。这样移动前后可以看成是同种状态,所以我们可以假设配对的棋子中前一个棋子是不动的,每次移动后一个棋子减少距离,谁能最后使配对的棋子全部紧挨谁就胜利(就同尼姆博弈中取走一堆中的石子一样,谁最后取完谁胜)。细心的人会发现,如果棋子数量是奇数怎么配对,如果从前往后配对,最后一个棋子的状态就不好固定了,所以从后往前陪。第一个棋子可以想象为与左边界配对。poj中的输入样例是从从小到大的,但题中没给出说明,所以我们要对输入的数据排序,才能得到正确的解。
开始看到此题的时候完全没有什么思路,但是看”寻找必败态——一类问题的快速解法“的解释如下:
分析:
乍一看这一题棋子移动还要受其他棋子的限制,好像无法求出通解,但仔细分析会发现别有洞天。
一个棋子每一次向左移的最大步数是固定的,而且随着移动减少,不是和取石子很像么?那么和取石子的区别在哪呢?就在于每一次移动时都会让右边相邻的那颗棋子移动空间变大,这样就和取石子只减不增有所不同了,我们应该怎样解决这个问题呢?
我们并不放弃将其与我们熟悉的取石子对应,但我们将策略做小小的变动:
将棋子从右端向左端每相邻两个分为一对,如果只剩一个就将棋盘左端加一格放一颗棋子与之配对,这样配对后好像和以前没有什么区别,但决策时就方便多了,因为我们大可不必关心组与组之间的距离,当对手移动一组中靠左边的棋子时,我们只需将靠右的那一颗移动相同步数即可!同时我们把每一组两颗棋子的距离视作一堆石子,在对手移动两颗棋子中靠右的那一颗时,我们就和他玩取石子游戏,这样就把本题与取石子对应上了。
本例说明有许多模型看似复杂,但经过一些巧妙的变换,便可以转化成一些我们熟悉的模型,同时也充分体现了博弈的灵活。
如果说前面的例子是介绍这种思想的运用的话,下面的方法就是讲这种思路的优越性了,因为这一题并不是走的“手推小数据=〉猜想=〉证明”的老路,而是直接利用性质推导必败条件。
其实看了上面的分析之后还不是很明白,在网上搜了一下如下的解释比较容易理解:
/*由于任何两个相邻的棋子只与他们之间的空位有关,所以可以转化为普通的Nim游戏:我们可以把这些空位看作是石子数,谁取得了最后一个空位,谁就是赢家。*/
我的代码
这里n==1的情况要单独考虑。
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
int main(void)
{
int t,n,a[10005],k,i,j;
scanf("%d",&t);
while(t--)
{
k=0;
a[0]=0;
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%d",&a[i]);
sort(a+1,a+n+1);
/*for(i=1;i<=n;i++)
{
for(j=i+1;j<=n;j++)
{
if(a[i]>a[j])
a[i]=a[i]^a[j]^(a[j]=a[i]);
}
}*/
if(n==1&&a[1]==1)
printf("Bob will win\n");
else if(n==1&&a[1]!=1)
printf("Georgia will win\n");
else
{
if(n%2)
{
for(i=1;i<=n;i++,i++)
k=k^(a[i]-a[i-1]-1);
}
else
{
for(i=2;i<=n;i++,i++)
k=k^(a[i]-a[i-1]-1);
}
if(k==0)
printf("Bob will win\n");
else
printf("Georgia will win\n");
}
}
return 0;
}
这题很好,是个好题。
标签:style http color io os ar strong for 数据
原文地址:http://www.cnblogs.com/liudehao/p/3989207.html