博弈论相关知识及其应用
组合游戏 Nim游& …
May all the beauty be blessed.
题目地址:洛谷:【P2148】[SDOI2009]E&D – 洛谷、BZOJ:Problem 1228. — [SDOI2009]E&D
小E 与小W 进行一项名为“E&D”游戏。
游戏的规则如下: 桌子上有2n 堆石子,编号为1..2n。其中,为了方便起见,我们将第2k-1 堆与第2k 堆 (1 ≤ k ≤ n)视为同一组。第i堆的石子个数用一个正整数Si表示。 一次分割操作指的是,从桌子上任取一堆石子,将其移走。然后分割它同一组的另一堆 石子,从中取出若干个石子放在被移走的位置,组成新的一堆。操作完成后,所有堆的石子 数必须保证大于0。显然,被分割的一堆的石子数至少要为2。 两个人轮流进行分割操作。如果轮到某人进行操作时,所有堆的石子数均为1,则此时 没有石子可以操作,判此人输掉比赛。
小E 进行第一次分割。他想知道,是否存在某种策 略使得他一定能战胜小W。因此,他求助于小F,也就是你,请你告诉他是否存在必胜策略。 例如,假设初始时桌子上有4 堆石子,数量分别为1,2,3,1。小E可以选择移走第1堆, 然后将第2堆分割(只能分出1 个石子)。接下来,小W 只能选择移走第4 堆,然后将第3 堆分割为1 和2。最后轮到小E,他只能移走后两堆中数量为1 的一堆,将另一堆分割为1 和1。这样,轮到小W 时,所有堆的数量均为1,则他输掉了比赛。故小E 存在必胜策略。
有一个游戏,给偶数堆石子,两个相邻石子为一组,游戏的每次操作要选出一组,将其中一堆石子移走并将另外一堆分成两堆作为新的一组石子。当一方无法进行操作的时候即输,求是否存在先手必胜策略。
输入格式:
第一行是一个正整数T(T ≤ 20),表示测试数据数量。接下来有T组 数据。 对于每组数据,第一行是一个正整数N,表示桌子上共有N堆石子。其中,输入数据保 证N是偶数。 第二行有N个正整数S1..SN,分别表示每一堆的石子数。
输出格式:
包含T 行。对于每组数据,如果小E 必胜,则输出一行”YES”,否则 输出”NO”。
输入样例#1:
2 4 1 2 3 1 6 1 1 1 1 1 1
输出样例#1:
YES NO
【数据规模和约定】
对于20%的数据,N = 2;
对于另外20%的数据,N ≤ 4,Si ≤ 50;
对于100%的数据,N ≤ 2×10^4 ,Si ≤ 2×10^9 。
参考资料:bzoj1228 [SDOI2009]E&D(博弈【规律) – CSDN博客
这里可以将一组石子视为一个独立的Nim游戏,根据SG定理,所有组的SG值异或和非0即为先手必胜,反之为先手必败。
一组石子的SG如何来求,我们当然可以采用爆搜,但是时间不允许。然后我们可以打个表,设SG(x, y)为一组的状态为(x, y)时的SG值,发现SG值有以下规律:
SG(x, y) = \begin{cases} 0, & x, y都是奇数 \\ SG(\lceil \frac{x}{2} \rceil, \lceil \frac{y}{2} \rceil) + 1, & x, y至少有一个是偶数 \end{cases}
于是现在这个搜索的复杂度就变成了O(\log n)的了,现在我们有了一个O(n \log n)的优秀做法。
关于这张表长什么样子,可以看参考资料那位同学的博文。
// Code by KSkun, 2018/5
#include <cstdio>
#include <cctype>
#include <cstring>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1;
register char c = fgc();
while(!isdigit(c)) {
if(c == '-') neg = -1;
c = fgc();
}
while(isdigit(c)) {
res = (res << 1) + (res << 3) + c - '0';
c = fgc();
}
return res * neg;
}
int T, n, x, y;
inline int sg(int x, int y) {
if((x & 1) && (y & 1)) return 0;
else if(!(x & 1) && !(y & 1)) return sg(x >> 1, y >> 1) + 1;
else if(x & 1) return sg((x + 1) >> 1, y >> 1) + 1;
else return sg(x >> 1, (y + 1) >> 1) + 1;
}
int main() {
T = readint();
while(T--) {
int res = 0;
n = readint() >> 1;
for(int i = 1; i <= n; i++) {
x = readint(); y = readint();
res ^= sg(x, y);
}
if(res) puts("YES"); else puts("NO");
}
return 0;
}
题目地址:洛谷:【P3265】[JLOI2015]装备购买 – 洛谷、BZOJ:Problem 4004. — [JLOI2015]装备购买
脸哥最近在玩一款神奇的游戏,这个游戏里有 n 件装备,每件装备有 m 个属性,用向量zi(aj ,…..,am) 表示 (1 <= i <= n; 1 <= j <= m),每个装备需要花费 ci,现在脸哥想买一些装备,但是脸哥很穷,所以总是盘算着怎样才能花尽量少的钱买尽量多的装备。对于脸哥来说,如果一件装备的属性能用购买的其他装备组合出(也就是说脸哥可以利用手上的这些装备组合出这件装备的效果),那么这件装备就没有买的必要了。
严格的定义是,如果脸哥买了 zi1,…..zip这 p 件装备,那么对于任意待决定的 zh,不存在 b1,….,bp 使得 b1zi1 + … + bpzip = zh(b 是实数),那么脸哥就会买 zh,否则 zh 对脸哥就是无用的了,自然不必购买。
举个例子,z1 =(1, 2, 3);z2 =(3, 4, 5);zh =(2, 3, 4),b1 =1/2,b2 =1/2,就有 b1z1 + b2z2 = zh,那么如果脸哥买了 z1 和 z2 就不会再买 zh 了。
脸哥想要在买下最多数量的装备的情况下花最少的钱,你能帮他算一下吗?
输入格式:
第一行两个数 n, m。接下来 n 行,每行 m 个数,其中第 i 行描述装备 i 的各项属性值。接下来一行 n 个数,其中 ci 表示购买第 i 件装备的花费。
输出格式:
一行两个数,第一个数表示能够购买的最多装备数量,第二个数表示在购买最多数量的装备的情况下的最小花费
输入样例#1:
3 3 1 2 3 3 4 5 2 3 4 1 1 2
输出样例#1:
2 2
如题目中描述,选择装备 1 装备 2,装备 1 装备 3,装备 2 装备 3 均可,但选择装备 1 和装备 2 的花费最小,为 2。
对于 100% 的数据, 1 <= n;m <= 500; 0 <= aj <= 1000。
在异或向量空间中的线性基维护算法的实质是一个高斯消元。对于常规的m维向量,同样也可以用类似的思路来维护。因此这个题对于线性基类问题来说应该算是裸题了。
似乎EPS有点卡精度,因此使用了long double。
// Code by KSkun, 2018/5
#include <cstdio>
#include <cctype>
#include <cmath>
#include <cstring>
#include <algorithm>
typedef long long LL;
typedef long double LD;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1;
register char c = fgc();
while(!isdigit(c)) {
if(c == '-') neg = -1;
c = fgc();
}
while(isdigit(c)) {
res = (res << 1) + (res << 3) + c - '0';
c = fgc();
}
return res * neg;
}
const int MAXN = 505;
const LD EPS = 1e-8;
int n, m;
struct Node {
LD vec[MAXN];
int cost;
inline bool operator<(const Node &rhs) const {
return cost < rhs.cost;
}
} equip[MAXN];
LD mat[MAXN][MAXN];
int main() {
n = readint(); m = readint();
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
equip[i].vec[j] = readint();
}
}
for(int i = 1; i <= n; i++) {
equip[i].cost = readint();
}
std::sort(equip + 1, equip + n + 1);
int cnt = 0, sum = 0;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
if(fabs(equip[i].vec[j]) > EPS) {
if(!mat[j][j]) {
memcpy(mat[j], equip[i].vec, sizeof(LD) * MAXN);
sum += equip[i].cost; cnt++;
break;
} else {
LD t = equip[i].vec[j] / mat[j][j];
for(int k = j; k <= m; k++) {
equip[i].vec[k] -= mat[j][k] * t;
}
}
}
}
}
printf("%d %d", cnt, sum);
return 0;
}