题意:一个n*m的棋盘,Burenka和Tonya进行博弈,Burenka先手。开始时,棋子位于左下角(1,1),每回合,每个人可以将棋子向上移动奇数个单位,或者向右移动奇数个单位。不能移动的人判输。
题解: n是奇数的话,则竖直方向能够移动的次数肯定是偶数次,如果是偶数的话,能够移动的次数就是奇数次,因为每次移动剩余行数的奇偶性都会变。
问题其实等价于有两堆n - 1 ,m - 1的石子,每个人可以从其中一堆拿走奇数个,没有石子可取则判输。先手输当且仅当n + m - 2 是奇数。
void solve() {
int n, m; cin >> n >> m;
if ((n ^ m) & 1) cout << &#34;Burenka\n&#34;;
else cout << &#34;Tonya\n&#34;;
}
2B. Mathematical Circus
题意:给定n, k(n为偶数),问能否将1~n分成n/2组(a_i,b_i),使得
4 \mid (a_i + k) * b_i
有解则输出YES,并输出一个解。否则输出NO。
题解:当k为奇数时,让所有奇数作为a,所有偶数作为b即可。
当k为4的倍数时,则无解。因为奇数无论放哪边,另一边必须是4的倍数,而奇数比4的倍数要多,因此无解。
当k \equiv 2 \mod 4时,有解。让所有4的倍数作为b_i和奇数配对。然后剩下的偶数作为a_i,和奇数配对。
void solve() {
int n, k;
cin >> n >> k;
if (k % 4 == 0) {
cout << &#34;NO\n&#34;;
return;
}
cout << &#34;YES\n&#34;;
if (k & 1) {
for (int i = 1;i <= n;i += 2) cout << i << &#34; &#34; << i + 1 << &#34;\n&#34;;
}
else {
for (int i = 2;i <= n;i += 2) {
if (i % 4 == 2) cout << i << &#34; &#34; << i - 1 << endl;
else cout << i - 1 << &#34; &#34; << i << endl;
}
}
}
2C. Fighting Tournament
题意:现有水平不同的 n 人打擂台赛,按编号排成一队(1~n), 第i个人的水平为a(水平为1~n的一个排列)。
每次队首的两个人进行比试。胜者将继续站在队首,败者直接排到队尾。
有q个询问,每次询问给定两个数k, i,问前k场比赛中,编号为i的选手赢了几局?
题解: 当水平最高的人在队首时,就一直是这个人赢。而n - 1场比赛后,水平最高的人一定轮到过。因此只需先模拟n次。但是还需要保存前n - 1场每一场赢多少次,只需记录哪一场赢就好了。
void solve() {
int n, q;
cin >> n >> q;
for (int i = 1;i < n;i++) que.push_back(i);
int pre = 0;
for (int i = 1;i < n;i++) {
auto& cur = que.front(); que.pop_front();
if (a[pre] > a[cur]) {
que.push_back(cur);
s[pre].push_back(i);
}
else {
que.push_back(pre);
pre = cur;
s[cur].push_back(i);
}
}
while (q--) {
int i, k;
cin >> i >> k;
--i;
if (k < n || a != n) {
cout << upper_bound(s.begin(), s.end(), k) - s.begin() << &#34;\n&#34;;
}
else {
cout << max(0, k + 1 - n) + (upper_bound(s.begin(), s.end(), k) - s.begin()) << &#34;\n&#34;;
}
}
}
2D. Burenka and Traditions
题意:有k种字符,第i个字符有a个。问能否用它组成一个Fibonacci字符串。即F个同样的字符按顺序拼接起来。
比如 abaabbbcccccaaaaaaaa
一项为一个a,然后1个b,接着2个a,3个b,5个c,8个a。
相邻的两项不能使用同一个字符。
题解:首先字符数总数是否为Fibonacci数列的前k项和,如果不是,那无解。如果是,那就相当于拿这些字符去填k个盒子,每个盒子只能装同一个盒子,并且相邻盒子不能使用同一个字符。
如果相邻的两项可以使用同一个字符,则可以使用贪心,每次选取剩余的字符中数目最大的去填。
为什么这样是可以的?
Fibonacci数列有以下性质:
\sum_{i}^k F + 1= F[k+2]\\
如果k为奇数
F[k] = F[1] + \sum_{i=1}^{\lfloor k/2 \rfloor } F[2i]\\
如果k为偶数
F[k] = \sum_{i=1}^{ k/2} F[2i - 1]\\
如果一个字符的数目不小于最大的那个盒子能容纳的数目。那么它必然要填充倒数第二大的那个,依此类推,它所填充的位置,必然存在某些位置的盒子大小之和等于当前最大的这个(归纳一下即可)。此时我们交换它们,最优性不变。
题目要求的是连续不能取同一个字符。我们只需在贪心的时候下一轮不考虑当前取的字符即可。我们使用一个优先队列,取完等下一轮处理完再放回优先队列即可。
为什么这样贪心是对的?
同样由上面Fibonacci数列的性质。如果我们当前轮不取它,那当前考虑的必然是Fibonacci的偶数项(否则无解),并且最大的数必须等于当前盒子大小,否则也是无解。这种情况下,也必然是从Fibonacci的第一项开始,填充所有间隔的盒子。此时它们的和等于当前盒子大小,交换一下,最优性不变。
const int N = 50;
bool inited = false;
vector<int64_t> f(N);
map<int64_t, int> mp;
void init() {
if (inited) return;
inited = true;
f[0] = f[1] = 1;
mp[1] = 0, mp[2] = 1;
int64_t sum = 2;
for (int i = 2; i < N; i++) {
f = f[i - 1] + f[i - 2];
sum += f;
mp[sum] = i;
}
}
void solve() {
init();
int n;
cin >> n;
vector<int> c(n);
int64_t sum = 0;
priority_queue<int> que;
for (auto& x : c) {
cin >> x;
sum += x;
que.push(x);
}
if (!mp.count(sum)) {
cout << &#34;NO\n&#34;;
return;
}
int last = 0;
bool success = true;
for (int i = mp[sum]; i >= 0; i--) {
if (que.empty()) {
success = false;
break;
}
int cur = que.top(); que.pop();
if (cur < f) {
success = false;
break;
}
if (last) que.push(last);
last = cur - f;
}
cout << (success ? &#34;YES&#34; : &#34;NO&#34;) << &#39;\n&#39;;
}