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

Combination Lock

题目链接:GYM 102832H

题目大意

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

思路

麻了写了一天。

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

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

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

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

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/GYM_102832H.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(40)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
历史上的今天:
2021-07-05 【luogu CF1100F】Ivan and Burgers
2021-07-05 【ybt金牌导航8-1-4】【luogu P4151】路径最大异或和 / 最大XOR和路径
2021-07-05 【ybt金牌导航8-1-3】【HDU 3949】K小异或和 / XOR
点击右上角即可分享
微信分享提示