传送门:http://poj.org/problem?id=3688
题意:金手指:有俩人玩一个取石子的游戏,你是裁判。游戏中有W块石头和N张卡片,卡片上分别写着数字Ai。玩家随机抽走一张卡片,按卡片上的数字从石头堆中取走相应数量的石头,如果石头不够,玩家重新抽卡片,取走最后一块石头的玩家获胜;如果石头堆为空仍然未分出胜负,则拿回所有石头和卡片重新开始。
现在先手玩家贿♂赂了你,请你帮他构造必胜条件。游戏中的卡片是固定的,但W可供你操作。问有多少小于或等于M的W满足要求。
分析:
推理与动态规划算法
如果W只能表示成特定的n张卡片上的数字之和,那么:
-
当n为偶数时,{先手一张,后手一张}循环n/2次拿完石头,后手玩家必胜。
-
当n为奇数时,{先手一张,后手一张}循环n/2 + 1次拿完石头,先手玩家必胜。
如果W既可以表示成奇数张卡片数字之和,也可以表示成偶数张卡片数字之和,则两人都可能获胜;或者说没有必胜决策。
如果W既无法表示成奇数张卡片数字之和,也无法表示成偶数张卡片数字之和,则W无法用卡片取完,两人会一直玩到天荒地老,你这个裁判就变成了灯泡,而且是长明灯。
所以对题目有用的只有第一个如果分支。
这里有两份代码,一份wrong,一份ac,若能找出不同,你就彻底明白这道题了
wrong code:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <algorithm>
using namespace std;
int main()
{
int n,m;
int num[10005];
bool dp[100010][2];
while(scanf("%d %d",&n,&m)==2&&n)
{
memset(num,0,sizeof(num));
memset(dp,false,sizeof(dp));
for(int i=0;i<n;i++)
{
scanf("%d",&num[i]);
}
sort(num,num+n);
int i,j;
int ans = 0;
for(i=0;i<n;i++)
dp[num[i]][1] = true;
for( i=1;i<n;i++)
{
for(j=m;j>num[i];j--)
{
if(dp[j-num[i]][0])
dp[j][1] = true;
if(dp[j-num[i]][1])
dp[j][0] = true;
}
}
for(i=1;i<=m;i++)
{
if(dp[i][1]&&(!dp[i][0]))ans++;
}
printf("%d\n",ans);
}
return 0;
}
AC code:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <algorithm>
using namespace std;
int main()
{
int n,m;
int num[10005];
bool dp[100010][2];
while(scanf("%d %d",&n,&m)==2&&n)
{
memset(num,0,sizeof(num));
memset(dp,false,sizeof(dp));
for(int i=0;i<n;i++)
{
scanf("%d",&num[i]);
}
//sort(num,num+n);//排不排序对这道题结果不影响,自己体会
int i,j;
int ans = 0;
for( i=0;i<n;i++)
{
for(j=m;j>num[i];j--)
{
if(dp[j-num[i]][0])
dp[j][1] = true;
if(dp[j-num[i]][1])
dp[j][0] = true;
}
dp[num[i]][1] = true;
}
for(i=1;i<=m;i++)
{
if(dp[i][1]&&(!dp[i][0]))ans++;
}
printf("%d\n",ans);
}
return 0;
}