zl程序教程

您现在的位置是:首页 >  其他

当前栏目

【GYM 102832H】【模板】Combination Lock(二分图博弈)

模板 二分 lock 博弈 gym combination
2023-09-27 14:28:25 时间

Combination Lock

题目链接:GYM 102832H

题目大意

给你一个密码锁(至多五位),告诉你一开始的样子,然后两个人轮流选一个位置往上或者往下拧一格。
然后要使得每次得到的数字之前没有出现过且不是给出的数字。
要你判断是否先手必胜。

思路

麻了写了一天。

这个是二分图博弈的可以说是模板把,毕竟这个密码锁每次操作必定奇偶变化应该能看出来(我除外)。

然后就是二分图博弈的样子:
一个二分图,然后从一个点开始每个人轮流把它按边移动,每次要移动到一个新的位置,无法移动着失败。

然后先是结论:如果这个图所有最大匹配的方案都包含了起点,那先手必胜,否则后手必胜。

然后是证明:
充分性:如果包含起点 S S S,那先手每一步如果跟着匹配边,那后手只能跟着走。因为如果不跟着走,那形成一套 S → p 1 → p 2 → p n S\rightarrow p_1\rightarrow p_2\rightarrow p_n Sp1p2pn,那匹配边就是 S − p 1 , p 2 − p 3 , . . . , p n − 2 − p n − 1 S-p_1,p_2-p_3,...,p_{n-2}-p_{n-1} Sp1,p2p3,...,pn2pn1
那我们完全可以集体右移: p 1 − p 2 , p 3 − p 4 , . . . , p n − 1 − p n p_1-p_2,p_3-p_4,...,p_{n-1}-p_{n} p1p2,p3p4,...,pn1pn,这样长度相同也是最大匹配,但是却不包含了 S S S
必要性:如果不包含起点 S S S,那对于一个不包含 S S S 的最大匹配。第一步肯定是走在匹配点上(不然这条边是可以放进最大匹配里面的),而后手沿着匹配边一直走的话,先手是走不出的。
因为如果最后一个是非匹配点 S → p 1 → p 2 → p n S\rightarrow p_1\rightarrow p_2\rightarrow p_n Sp1p2pn,匹配是 p 1 − p 2 , . . . p n − 2 − p n − 1 p_1-p_2,...p_{n-2}-p_{n-1} p1p2,...pn2pn1,你完全可以左右各扩展一下得到 S − p 1 , p 2 − p 3 , . . . , p n − 1 − p n S-p_1,p_2-p_3,...,p_{n-1}-p_n Sp1,p2p3,...,pn1pn,那就不是最大匹配了。

然后接着考虑如何判断一个点是否一定在最大匹配中。
其实有一个最直观的办法(也就是我这里用的),就是去掉这个点跑一遍,再加上这个点跑一遍,看两次的最大匹配是否相同。(这里你可以不给之前的边流量重新复制,那你就只需要判第二次是否有流量即可)
然后还有一种方法就是你可以先跑出一个最大匹配,然后看看是否存在取代边(就是一条匹配边的一个点和一个非匹配的点有连边,那另一个点可以被取代)。
然后新被取代的又可以去取代别人,然后每个没有被匹配的都这么试一下,就可以得到那些点可以被取代哪些不可以里。

代码

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")

#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define INF 0x3f3f3f3f

using namespace std;

const int N = 1e5 + 5;
int m, n, ST, M, a[] = {1, 10, 100, 1000, 10000, 100000}, le[N], KK;
int tot, S, T, deg[N], lee[N];
bool sp[N], odd[N];
queue <int> q;
struct node {
	int x, to, nxt, op;
}e[N * 20];

int read() {
	int re = 0; char c = getchar();
	while (c < '0' || c > '9') c = getchar();
	while (c >= '0' && c <= '9') {
		re = (re << 3) + (re << 1) + c - '0';
		c = getchar();
	}
	return re;
}

void Init() {
	KK = 0; for (int i = 0; i < N; i++) le[i] = -1;
	memset(sp, 0, sizeof(sp));
}

void link(int x, int y, int z) {
	e[++KK] = (node){z, y, le[x], KK + 1}; le[x] = KK; e[++KK] = (node){0, x, le[y], KK - 1}; le[y] = KK;
}

void calc(int i) {
	for(int j = 0; j < m; j++) {
		int bit = i / a[j] % 10, x;
		if(bit != 9) x = i + a[j];
			else x = i - 9 * a[j];
		if (!sp[x]) link(i, x, 1);

		if (bit) x = i - a[j];
			else x = i + 9 * a[j];
		if (!sp[x]) link(i, x, 1);
	}
}

bool bfs() {
	for (int i = 0; i <= tot; i++) lee[i] = le[i], deg[i] = 0;
	q.push(S); deg[S] = 1;
	while (!q.empty()) {
		int now = q.front(); q.pop();
		for (int i = le[now]; ~i; i = e[i].nxt)
			if (!deg[e[i].to] && e[i].x) {
				deg[e[i].to] = deg[now] + 1;
				q.push(e[i].to);
			}
	}
	return deg[T];
}

int dfs(int now, int sum) {
	if (now == T) return sum;
	int go = 0;
	for (int &i = lee[now]; ~i; i = e[i].nxt)
		if (deg[e[i].to] == deg[now] + 1 && e[i].x) {
			int this_go = dfs(e[i].to, min(e[i].x, sum - go));
			if (this_go) {
				e[i].x -= this_go; e[e[i].op].x += this_go;
				go += this_go; if (go == sum) return go;
			}
		}
	if (go == 0) deg[now] = 0;
	return go;
}

int dinic() {
	int re = 0; while (bfs()) re += dfs(S, INF); return re;
}

int main() {
	for (int i = 0; i < N; i++) {
		int id = 0, x = i; while (x) {id += x % 10; x /= 10;} odd[i] = id & 1;
	}
	
	int TT = read();
	while (TT--) {
		Init();
		
		m = read(); n = read(); ST = read();
		M = a[m]; tot = M; S = ++tot; T = ++tot;
		for (int i = 1; i <= n; i++) {
			int x = read(); sp[x] = 1;
		}
		for(int i = 0; i < M; i++) {
			if(sp[i]) continue;
			if(odd[i] == odd[ST]) {
				calc(i);
				if (i != ST) link(S, i, 1);
			}
			else {
				link(i, T, 1);	
			}
		}
		dinic();
		link(S, ST, 1);
		if (dinic()) printf("Alice\n");
			else printf("Bob\n");
	}
	
	return 0;
}