bzoj 3105: [cqoi2013]新Nim游戏

博主分享第一道线性基题目题解。考虑第二回合结束后异或和不为0的条件,将问题转变为选几堆棋子使任意选法异或和为正。推理中利用x ^ y <= x + y性质,从大到小插入元素到线性基,插入成功则元素被选出,并给出正确性伪证。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述

题目

作为我的第一道线性基题目,我觉得可以写一写题解。

考虑第二回合结束之后的情况,必须满足异或和不为0(博弈论的一般条件)。

于是可以发现,先手是尽量满足,对于第一回合结束后的状态,无论如何选取(不取玩),余下的堆异或和为正。

为了满足先手的做功最少,第一回合结束后棋子数量必须最多。

于是问题转变为:对于一些棋子,选出其中的几堆使得在这几堆中,任意的选法(不能不选)得到的异或和为正。

在我推理的时候发现一个性质:x ^ y <= x + y(易证)(如果你说x, y, 是复数或者实数我也没有办法,请你关闭这个网页吧)

于是考虑从大到小插入元素到线性基当中,如果插入成功,说明这一个元素是被选出来的。

正确性的伪证:

为什么要讲一个元素插入到线性基当中?

因为插入之后可以最大化答案(参见上面的问题转变为)。但是考虑到插入之后可能导致之后的元素无法插入,所以运用上面可能让你关网页的那个性质,对于x, y, z(x > y > z),如果满足y ^ z = x,一定是x与y被插入。开始的时候我写的是从小到大,于是全部WA了。

Code:

#include <bits/stdc++.h>
#define xx first
#define yy second
#define ll long long
#define _for(i,a,b) for(int i=(a);i<=(b);++i)

using namespace std;

struct _in {
	const _in &operator, (int &a) const {
		a = 0;
		char k = getchar ();
		int f = 1;
		while (k > '9' || k < '0') {
			if (k == '-') {
				f = -1;
			}
			k = getchar ();
		}
		while (k >= '0' && k <= '9') {
			a = a * 10 + k - '0';
			k = getchar ();
		}
		a *= f;
		return *this;
	}
}in;

inline int readint () {
	int x;
	in, x;
	return x;
}

const int N = 105;
ll info[N], n;
ll d[N];
ll sum, ans;

inline void insert (int u) {
	ll tmp = u;
	for (int i = 31; i >= 1; --i) {
		if (u & (1 << (i - 1))) {
			if (d[i]) {
				u ^= d[i];
			}
			else {
				d[i] = u;
				ans += tmp;
				return ;
			}
		}
	}
}

int main () {
	scanf ("%d", &n);
	for (int i = 1; i <= n; ++i) {
		scanf ("%d", &info[i]);
	}
	sort (info + 1, info + n + 1);
	for (int i = n; i >= 1; --i) {//注意从n到1
		sum += info[i];
		if (info[i] != info[i - 1]) {//可以证明,重复元素只会留下一个
			insert (info[i]);
		}
	}
	printf ("%lld\n", sum - ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值