CF1893E题解

  • 分析

    第一眼:博弈论。
    第二眼:呃……贪心?
    实际:DP。
    首先想这个游戏大抵存在必胜策略,否则不会让我们求。

    思考先手必胜条件,就是如何让这个数组最后只剩下一个数。
    设数列之和为\(sum\)
    发现每次操作给两个数减的数字是一样的。那么对于每次操作,\(\Delta sum\)都为两者之间更少的数的二倍。
    所以假设每次取的更小的数为 \(x\),那么如果\(\exists\sum x= \frac{sum}{2}\),那么数组最后的两个数就会在一次操作后都为零,显然此时先手不必胜。
    而如果不为二分之一,在最后一次操作就会出现只剩下一个数的情况,所以此时先手必胜,而且无论如何取都会赢。

    思考如何计算每次取的更小的数的和 \(\sum x\)
    每次取的更小的数有两种情况,一种是原本的 \(a_i\),一种是 \(a_i\) 减过几次的 \(a_{i}'\),因为更小的数是减数的集合,所以对于每个减过的数都会存储在这个集合中,所以对于每个减数“碎片”都存在于集合。
    如果“碎片”是\(a_{i}\),那么在集合里删掉这个数,将“碎片”拼回被减数。
    如果“碎片”是\(a_{i}'\),那么我们可以任意保留\(a_{i}\)或者原被减数中的一个,因为一定存在一种方案可以将所有碎片都拼回去。
    为什么?因为我们考虑一个个放回去,最后得到的初始取数集合一定是全是\(a_{i}\)(或者说你取的第一个数不可能是“碎片”),即不存在任何“碎片”的集合,所以“碎片”一定都会拼回去。
    所以\(\exists\sum x= \frac{sum}{2}\)就变成了\(\exists\sum a_{i}= \frac{sum}{2}\)

    思考后手必胜情况。
    因为已知\(\sum x\)\(\sum a_i\),所以我们每次是减一个在这个集合里的数,同时也要减一个不在这个集合里的数。
    所以我们可以每次选一个合法且与先手选的数所在集合不同的数即可。

    然后我们现在要找一个集合使得\(\sum a_i = \frac{sum}{2}\)
    用背包可以解决,在转移的时候使用bitset可以存储取数情况。
    空间复杂度\(\mathcal{O(n^3)}\),时间复杂度\(\mathcal{O(\frac{n^4}{w})}\),可以通过本题。

  • 代码

#include <iostream>
#include <bitset>
using namespace std;
constexpr int MAXN(307);
bitset <MAXN> fs[MAXN * MAXN];
int a[MAXN], f[MAXN * MAXN], blg[MAXN];
int n, sum, t;
inline void read(int &temp) { cin >> temp; }
int main() {
	ios::sync_with_stdio(false);
	read(n);
	for (int i(1); i <= n; ++i)  read(a[i]), sum += a[i];
	f[0] = 1;
	for (int i(1); i <= n; ++i) {
		for (int j(sum); j >= a[i]; --j) {
			if (f[j])  continue;
			f[j] |= f[j - a[i]];
			if (!f[j])  continue;
			fs[j] = fs[j - a[i]], fs[j][i] = 1;
		}
	}
	if (sum % 2 == 0 && f[sum / 2]) {
		for (int i(1); i <= n; ++i)  if (fs[sum / 2][i])  blg[i] = 1;
		cout << "Second" << endl;
		while (1) {
			read(t);
			if (!t)  break;
			for (int j(1); j <= n; ++j) {
				if (blg[j] == (blg[t] ^ 1) && a[j] && j != t) {
					cout << j << endl;
					int mns = min(a[t], a[j]);
					a[t] -= mns, a[j] -= mns;
					break;
				}
			}
		}
	}
	else {
		cout << "First" << endl;
		while (1) {
			int cz(0);
			for (int j(1); j <= n; ++j) {
				if (a[j]) {
					cout << j << endl;
					cz = j;
					break;
				}
			}
			read(t);
			if (!t)  break;
			int mns = min(a[t], a[cz]);
			a[t] -= mns, a[cz] -= mns;
		}
	}
	return 0;
}
posted @ 2023-10-23 17:38  Kazdale  阅读(184)  评论(0编辑  收藏  举报