题意
传送门 POJ 3688
题解
博弈问题,考虑胜负态转移;一般的必胜态只要判断是否能转移到必败态即可,但这个游戏必须要保证必胜态可转移到的非重赛的状态都是必败态。在这个游戏中,存在 4 4 4 种状态:必胜态、必败态、胜负都可能的状态、需要重赛的状态。需要求解的是必胜态的个数。
可以发现先手或后手获胜的情况只与牌上数字的组合有关,与出牌顺序无关。考虑到先手石子堆没有石子时为必败态,那么有:先手必胜时,石子堆的个数只能由奇数个牌上的数字组成。
d
p
[
i
]
[
j
]
[
k
]
dp[i][j][k]
dp[i][j][k] 代表用序号为
[
0
,
i
]
[0,i]
[0,i] 的牌能构成数字
k
k
k 时,牌数能否是奇数(
j
=
1
j=1
j=1)或偶数(
j
=
0
j=0
j=0),设第
i
i
i 张牌上的数字为
A
[
i
]
A[i]
A[i]
d
p
[
i
]
[
j
]
[
k
]
=
d
p
[
i
−
1
]
[
j
]
[
k
]
∣
d
p
[
i
−
1
]
[
j
⊕
1
]
[
k
−
A
[
i
]
]
dp[i][j][k]=dp[i-1][j][k]\ |\ dp[i-1][j\oplus 1][k-A[i]]
dp[i][j][k]=dp[i−1][j][k] ∣ dp[i−1][j⊕1][k−A[i]] 实现上,
D
P
DP
DP 数组可以压缩为
O
(
M
)
O(M)
O(M)。
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
using namespace std;
#define maxn 10005
#define maxm 100005
int N, M, A[maxn];
bool dp[2][maxm];
int main()
{
while (~scanf("%d%d", &N, &M) && (N | M))
{
for (int i = 0; i < N; ++i)
scanf("%d", A + i);
memset(dp, 0, sizeof(dp));
dp[0][0] = 1;
for (int i = 0; i < N; ++i)
{
for (int j = M; j >= A[i]; --j)
{
dp[0][j] |= dp[1][j - A[i]];
dp[1][j] |= dp[0][j - A[i]];
}
}
int res = 0;
for (int i = 1; i <= M; ++i)
{
if (dp[1][i] && !dp[0][i])
++res;
}
printf("%d\n", res);
}
return 0;
}