zoukankan      html  css  js  c++  java
  • [学习笔记]一类博弈问题与图的匹配的联系

    引入

    • 经典问题:一个无向图,双方轮流选出一个点,一个点最多被选出一次,且每次选出的点必须和上一次对方选出的点相邻

    • 不能动者输

    • 双方都绝顶聪明,求先手是否有必胜策略,以及先手第一次选哪些点能必胜

    结论

    • 先手第一次选 (u) 必胜当且仅当原图存在一个不包含 (u)最大匹配

    • 故先手必胜当且仅当这个图没有完美匹配

    证明

    • 考虑归纳

    • 只有一个点显然先手必胜

    • 若原图存在一个最大匹配不包含 (u),则随便找一组不包含 (u) 的最大匹配

    • 由于是最大匹配,所以对于 (u) 相邻的所有点 (v) 都满足删掉点 (u) 之后,(v) 被剩下的图所有的最大匹配包含(否则可以连上 ((u,v)) 得到更大的匹配)

    • 故后手不管下一次选哪个点都是被所有最大匹配包含的

    • 若原图所有的最大匹配都包含 (u),还是随便找一组最大匹配

    • 而删掉这个点 (u) 之后,这个图的最大匹配数会减 (1),即包含 (u) 的匹配边会被删掉,使得原先与 (u) 匹配的点被移出匹配点

    • 故后手移到 (u) 的匹配点即可

    • Q.E.D

    校内模拟题 卡片游戏

    Statement

    • (n) 种卡片,第 (i) 种卡片有 (q_i) 个,有一个属性值 (p_i)

    • Alice 和 Bob 轮流取卡片

    • 每个人取的卡片的属性值 (a) 必须满足:若对方上一次取得卡片属性值为 (b),则 (frac{max(a,b)}{min(a,b)}) 为质数

    • 求 Alice 先取哪些卡片能必胜

    • 多组数据,数据组数不超过 (100)

    • (1le nle 500)(1le p_ile 5 imes10^{10})(1le q_ile 10^9),每组数据内 (p) 互不相同

    Solution

    • 考虑暴力把游戏图建出来,令 (cnt_i) 表示 (p_i) 的质因子个数

    • 一条边连接的两端 (cnt) 之差绝对值为 (1),故这是一个二分图

    • 由于 (q) 很大,所以需要把所有 (q_i) 个点建成一个,具体地:

    • (1)源向 (cnt_i) 为奇数的点连边,容量为 (q_i)

    • (2)(cnt_i) 为偶数的点向汇连边,容量为 (q_i)

    • (3)如果 (|cnt_i-cnt_j|=1),并且有 (p_i|p_j)(p_j|p_i)

    • 注意到每个点度数的上界只有 (p) 的质因子个数,所以这张图的边数远不达 (O(n^2)),跑网络流的复杂度是可以接受的

    • 这样 Alice 能取第 (i) 张卡片当且仅当源连向 (i)(i) 连向汇的边不一定流满

    • 判断一条边 (<u,v>) 是否可以不满的方法:

    • (1)如果原图跑完最大流后这条边不满则直接判掉

    • (2)否则找一条汇到源的增广路,恰好经过 (<v,u>) 一次,沿这条路径从汇退给源 (1) 的流量,相当于同时把总流量和 (<u,v>) 的流量都减了 (1)

    • (3)然后若有源到汇的增广路则这条边可以不满,否则这条边一定流满

    • 此外,求 (cnt_i) 需要特殊的技巧:先用 ([2,4000]) 内的质数去筛 (p_i)(p_i) 被筛完之后得到的数最多只有 (2) 个质因子,这时可以使用 Miller-Rabin 素数测试来判定其贡献了多少个质因子,判定 (frac{p_i}{p_j})(p_i>p_j))是否为质数只需判定 (cnt_i=cnt_j+1)(p_j|p_i) 这两个条件

    Code

    #include <bits/stdc++.h>
    
    template <class T>
    inline void read(T &res)
    {
    	res = 0; bool bo = 0; char c;
    	while (((c = getchar()) < '0' || c > '9') && c != '-');
    	if (c == '-') bo = 1; else res = c - 48;
    	while ((c = getchar()) >= '0' && c <= '9')
    		res = (res << 3) + (res << 1) + (c - 48);
    	if (bo) res = ~res + 1;
    }
    
    template <class T>
    inline T Min(const T &a, const T &b) {return a < b ? a : b;}
    
    typedef long long ll;
    
    const int N = 510, M = 4005, L = 1e6 + 5, INF = 0x3f3f3f3f;
    const ll INFll = 1145141919810114514ll;
    
    int n, q[N], tot, pri[M], cnt[N], ecnt, nxt[L], adj[N], go[L], cap[L], S, T,
    cur[N], lev[N], len, que[N], anst, wh[N];
    ll p[N], ans[N];
    bool vis[M], siv[N];
    
    void add_edge(int u, int v, int w)
    {
    	nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v; cap[ecnt] = w;
    	nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u; cap[ecnt] = 0;
    }
    
    ll mul(ll a, ll b, ll zyy) {return ((__int128) a) * b % zyy;}
    
    ll qpow(ll a, ll b, ll zyy)
    {
    	ll res = 1;
    	while (b)
    	{
    		if (b & 1) res = mul(res, a, zyy);
    		a = mul(a, a, zyy);
    		b >>= 1;
    	}
    	return res;
    }
    
    bool prime(ll num)
    {
    	if (num <= 4000) return !vis[num];
    	if (!(num & 1)) return 0;
    	ll tmp = num - 1; int cnt = 0;
    	while (!(tmp & 1)) tmp >>= 1, cnt++;
    	for (int i = 1; i <= 10; i++)
    	{
    		ll x = qpow(pri[i], tmp, num);
    		for (int i = 1; i <= cnt; i++)
    		{
    			ll y = mul(x, x, num);
    			if (y == 1 && x != 1 && x != num - 1) return 0;
    			x = y;
    		}
    		if (x != 1) return 0;
    	}
    	return 1;
    }
    
    int calc(ll num)
    {
    	int res = 0;
    	for (int i = 1; i <= tot; i++)
    		while (num % pri[i] == 0) num /= pri[i], res++;
    	return num > 1 ? res + 1 + (!prime(num)) : res;
    }
    
    bool bfs()
    {
    	for (int i = 1; i <= n + 2; i++) lev[i] = -1, cur[i] = adj[i];
    	lev[que[len = 1] = S] = 0;
    	for (int i = 1; i <= len; i++)
    	{
    		int u = que[i];
    		for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
    			if (cap[e] && lev[v] == -1)
    			{
    				lev[que[++len] = v] = lev[u] + 1;
    				if (v == T) return 1;
    			}
    	}
    	return 0;
    }
    
    ll dinic(int u, ll flow)
    {
    	if (u == T) return flow;
    	ll res = 0, delta;
    	for (int &e = cur[u], v = go[e]; e; e = nxt[e], v = go[e])
    		if (cap[e] && lev[u] < lev[v])
    		{
    			delta = dinic(v, Min(1ll * cap[e], flow - res));
    			if (delta)
    			{
    				cap[e] -= delta; cap[e ^ 1] += delta;
    				res += delta; if (res == flow) break;
    			}
    		}
    	if (res < flow) lev[u] = -1;
    	return res;
    }
    
    bool sfd(int u, int tar)
    {
    	if (u == tar) return 1;
    	siv[u] = 1;
    	for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
    		if (cap[e] && !siv[v] && sfd(v, tar)) return 1;
    	return 0;
    }
    
    void work()
    {
    	read(n);
    	for (int i = 1; i <= n; i++) read(p[i]), read(q[i]),
    		cnt[i] = calc(p[i]);
    	ecnt = 1; S = n + 1; T = n + 2;
    	memset(adj, 0, sizeof(adj));
    	for (int i = 1; i <= n; i++)
    		for (int j = 1; j <= n; j++)
    			if (cnt[i] + 1 == cnt[j])
    			{
    				if (p[j] % p[i]) continue;
    				if (cnt[i] & 1) add_edge(i, j, INF);
    				else add_edge(j, i, INF);
    			}
    	memset(siv, 0, sizeof(siv));
    	for (int i = 1; i <= n; i++)
    		if (cnt[i] & 1) add_edge(S, i, q[i]), wh[i] = ecnt - 1;
    		else add_edge(i, T, q[i]), wh[i] = ecnt - 1;
    	while (bfs()) dinic(S, INFll); anst = 0;
    	for (int u = 1; u <= n; u++)
    	{
    		if (cap[wh[u]]) {ans[++anst] = p[u]; continue;}
    		memset(siv, 0, sizeof(siv));
    		int e1 = -1, e2, e3;
    		for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
    		{
    			if (v > n || (cnt[u] & 1) == (cnt[v] & 1)) continue;
    			if ((cnt[u] & 1) && cap[e ^ 1] && cap[wh[v] ^ 1])
    				{e1 = wh[u]; e2 = e; e3 = wh[v]; break;}
    			if (!(cnt[u] & 1) && cap[e] && cap[wh[v] ^ 1])
    				{e1 = wh[v]; e2 = e ^ 1; e3 = wh[u]; break;}
    		}
    		cap[e1]++; cap[e1 ^ 1]--; cap[e2]++; cap[e2 ^ 1]--; cap[e3]++; cap[e3 ^ 1]--;
    		cap[cnt[u] & 1 ? e1 : e3]--; if (sfd(S, T)) ans[++anst] = p[u];
    		cap[cnt[u] & 1 ? e1 : e3]++;
    		cap[e1]--; cap[e1 ^ 1]++; cap[e2]--; cap[e2 ^ 1]++; cap[e3]--; cap[e3 ^ 1]++;
    	}
    	std::sort(ans + 1, ans + anst + 1);
    	for (int i = 1; i <= anst; i++) printf("%lld ", ans[i]);
    	puts("");
    }
    
    int main()
    {
    	#ifdef nealchentxdy
    	#else
    		freopen("game.in", "r", stdin);
    		freopen("game.out", "w", stdout);
    	#endif
    	
    	int T;
    	for (int i = 2; i <= 4000; i++) if (!vis[i])
    		for (int j = i * i; j <= 4000; j += i)
    			vis[j] = 1;
    	for (int i = 2; i <= 4000; i++) if (!vis[i]) pri[++tot] = i;
    	read(T);
    	while (T--) work();
    	return 0;
    }
    

    ZROI 树上游戏

    Statement

    • 有一棵 (n) 个点的有根树,(1) 为根,双方轮流操作

    • 每次选出一个点,这个点必须和上一次对方选出的点有祖先后代关系,每个点都不能被选超过一次

    • 求这棵树有多少个点的子集,满足这个子集内的点不能被选出的限制下,先手必胜,对 (998244353) 取模

    • (nle 2000)

    Solution

    • 还是一样,一个新图,树上有祖先后代关系的点之间连边,先手必胜当且仅当可选的点集没有完美匹配

    • 由于这是一棵树,考虑贪心匹配,即对于一个子树,贪心地让子树内配成的对数最多(剩下的点数最少)

    • (f[u][i]) 表示 (u) 的子树内的点集有多少个合法的子集,使得剩下的点数为 (i)

    • 转移时先把所有子节点的 DP 数组进行背包合并

    • 然后讨论 (u) 是否能被选出:(u) 能被选出则 (ileftarrow|i-1|),否则 (i) 不变

    • 答案为 (sum_{i=1}^nf[1][i])

    • 由树上背包合并的经典复杂度分析得到复杂度 (O(n^2))

    Code

    #include <bits/stdc++.h>
    
    template <class T>
    inline void read(T &res)
    {
    	res = 0; bool bo = 0; char c;
    	while (((c = getchar()) < '0' || c > '9') && c != '-');
    	if (c == '-') bo = 1; else res = c - 48;
    	while ((c = getchar()) >= '0' && c <= '9')
    		res = (res << 3) + (res << 1) + (c - 48);
    	if (bo) res = ~res + 1;
    }
    
    const int N = 2005, M = N << 1, rqy = 998244353;
    
    int n, ecnt, nxt[M], adj[N], go[M], f[N][N], sze[N], tmp[N], ans;
    
    void add_edge(int u, int v)
    {
    	nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
    	nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u;
    }
    
    void dfs(int u, int fu)
    {
    	f[u][0] = 1;
    	for (int e = adj[u], v; e; e = nxt[e])
    		if ((v = go[e]) != fu)
    		{
    			dfs(v, u);
    			for (int i = 0; i <= sze[u] + sze[v]; i++) tmp[i] = 0;
    			for (int i = 0; i <= sze[u]; i++)
    				for (int j = 0; j <= sze[v]; j++)
    					tmp[i + j] = (1ll * f[u][i] * f[v][j] + tmp[i + j]) % rqy;
    			for (int i = 0; i <= sze[u] + sze[v]; i++) f[u][i] = tmp[i];
    			sze[u] += sze[v];
    		}
    	sze[u]++;
    	int tm = f[u][0];
    	for (int i = 0; i < sze[u]; i++) f[u][i] = (f[u][i] + f[u][i + 1]) % rqy;
    	f[u][1] = (f[u][1] + tm) % rqy;
    }
    
    int main()
    {
    	#ifdef zhouzhouzka
    	#else
    		freopen("game.in", "r", stdin);
    		freopen("game.out", "w", stdout);
    	#endif
    	
    	int x, y;
    	read(n);
    	for (int i = 1; i < n; i++) read(x), read(y), add_edge(x, y);
    	dfs(1, 0);
    	for (int i = 1; i <= n; i++) ans = (ans + f[1][i]) % rqy;
    	return std::cout << ans << std::endl, 0;
    }
    
  • 相关阅读:
    可能会搞砸你的面试:你知道一个TCP连接上能发起多少个HTTP请求吗?
    iOS笔记055
    iOS笔记053- Quartz2D-练习
    iOS笔记052- Quartz2D-绘图
    IOS笔记051-手势使用
    IOS笔记050-事件处理
    IOS笔记049-UITabBarController
    IOS笔记048-数据存储
    IOS笔记047-代理传值和block传值
    IOS笔记046-UIApplication/导航控制器
  • 原文地址:https://www.cnblogs.com/xyz32768/p/12368655.html
Copyright © 2011-2022 走看看