题目链接: Charmed by the Game
大致题意
A和B玩一个游戏, 两个人轮流发球(最初时, 可以A先发, 也可以B先发).
如果A发球, B接住了, 称为"破发", 反之依然.
如果A发球, B没接住, A赢. 若B接住了, B赢. 反之依然.
现给出A和B胜利的次数, 问可能出现的破发情况有多少种, 并输出每种情况下的破发次数.
解题思路
思维 (感觉这个题思维量真的高)
首先我们考虑, 其实发球情况只有两种, 一种是A先手, 另外一种是B先手. 我们不妨讨论A先手发球的情况, 另外一种同理可得.
我们不妨先求出最大破发数. 即: 如果当前是A发球, 看看B是否还能胜利, 如果能, 记录破发. 反之亦然.
然后考虑减小破发数: 假设当前A发球, B破发了, 我们改变结果, 变成A发球, B未破发. 此时胜利情况并不符合题意, 因此我们应在A的胜局中选出一局, 让B赢. 考虑到A胜局有两种情况, 一种是B发球, A破发的情况, 另外一种是A发球, B未破发的情况. 如果我们修改第二种情况, 并不能减小破发数, 因此我们只能修改第一种情况. 这样, 我们发现当前情况的破发数, 比原先减少了2.
我们发现: 如果想要减少破发数, 我们至少需要改变两场A破发, 且B也破发的局面. 那么最大可以减小的次数即为: m i n ( A 破 发 次 数 , B 破 发 次 数 ) min(A破发次数, B破发次数) min(A破发次数,B破发次数).
我看有大佬用 01 01 01序列抽象出了模型, 这里也可以简单说一下(因为代码也是这样实现的):
假设我们发球序列用 1 1 1表示A发球, 0 0 0表示B发球. 结果序列用 1 1 1表示破发, 0 0 0表示未破发.
两种情况下的发球序列都是固定的, 我们无法更改. 我们只能改变结果序列.
我们要减小破发数, 相当于把结果序列中的 1 1 1变成 0 0 0, 并且还需要保证胜利情况不改变. 因此我们必须改变偶数次结果序列, 使得序列中的 1 1 1变成 0 0 0. 且每两次改变, 应该改变A和B两个人的破发结果.
AC代码
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 2E5 + 10;
int a[N];
set<int> st;
void fact(int n, int m, bool flag) {
int all = n + m;
rep(i, all) a[i] = (i & 1) ^ flag;
int ba = 0, bb = 0; //记录A和B的破发次数
rep(i, all) {
if (a[i] == 1) { //A发球
if (m) m--, bb++;
else n--;
}
else {
if (n) n--, ba++;
else m--;
}
}
int fmax = ba + bb;
st.insert(fmax);
int can = min(ba, bb);
rep(i, can) st.insert(fmax - i * 2);
}
int main()
{
int t; cin >> t;
while (t--) {
int n, m; scanf("%d %d", &n, &m);
st.clear();
fact(n, m, 0);
fact(n, m, 1);
printf("%u\n", st.size());
for (auto& op : st) printf("%d ", op); puts("");
}
return 0;
}