JZOJ5388. 博弈

题目大意


简化以后就是,两个人在树上博弈.A在树上走,走过的边被标记,B可以解除标记或删除一条边.问A从起点到终点最少走几步.

解题思路

设起点是\(s\),中点是\(t\),\(P(x,y)\)是树中从\(x\)\(y\)的路径,则整个游戏一定是这样的:A一头扎进\(P(s,t)\)中除\(t\)以外(包括\(s\))的任意一个点的一个子树,然后被困在一个点\(k\)无路可走,此时B删掉除\(P(k,t)\)连着的所有边,最后解除标记,让A一步一步走向\(t\).

假设此时A扎进了一个子树, B最少走几步可以让A停下呢?
不妨设\(f_x\)为若A进入到以\(x\)为根的子树中, B需要几步才能让A停下来, 且停在\(x\)的位置.
那么B的最优策略一定是:

  1. 在A到了\(x\)点后, 割掉连接以\(x\)为根的\(f\)值最大的子树的边, 让A进入\(f\)值第二大的子树中. 此步的代价为\(f_{y}+1(y\text{是}x\text{的}f\text{值第二大的儿子})\).
  2. 此时A出不来了, B趁机把连接所有其它子树的边都删了. 此步的代价\(son_x-2(son_x\text{表示}x\text{的儿子数量})\)
  3. 最后解除标记让A回到\(x\). 此步的代价为\(1\).
    总代价为\(f_y+son_x\)
    也就是转移方程

\[f_x=f_y+son_x(y\text{是}x\text{的}f\text{值第二大的儿子}) \]

\(s\)为根建树, 求出\(P(s,t)\)上每一个子树中每一个点的\(f\)值. 现在我们来求总答案.
总答案不好求, 考虑二分答案.
如何判合法? 先预处理出\(P(s,t)\), 存下来.
枚举\(P(s,t)\)上的点, 设当前操作数为\(cnt\), 二分的答案为\(c\), A在\(P(s,t)\)上走的步数(即当前点到\(s\)的距离)(不难发现这就是B此时最多的操作数).
显然一些子树我们必须砍去, 否则答案就会超过\(c\), 若B的剩余操作次数不够砍, 就不合法, 这可以作为判定的一个标准, 第二个标准是\(cnt\)不能大于\(c\).
B在让A进入一个子树后显然要趁着A不能出来的机会把\(P(s,t)\)上剩下的子树全删了, 不然A出来以后又钻进去其它子树怎么办= =.. 所以判断的时候还要加上\(P(s,t)\)的总子树数(当然不包括\(t\)的子树).

具体自己想想.. 我太懒了

代码参考了dh大佬的标.

#include <cstdio>
#define N 1000010
#define fo(i, a, b) for(int i = (a); i <= (b); ++i)
#define fd(i, a, b) for(int i = (a); i >= (b); --i)
#define fe(i, u) for(int i = last[u], v = to[last[u]]; i; v = to[i = pre[i]]) if(v ^ fa[u])
inline int read()
{
	int x = 0; char ch = getchar();
	while(ch < '0' || ch > '9')	ch = getchar();
	while(ch >= '0' && ch <= '9')	x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
	return x;
}
int n, s, t, m, sum, fa[N], w[N], p[N], son[N], last[N], pre[N << 1], to[N << 1];
bool inp[N];
inline void add(int u, int v){static int tot = 0; to[++tot] = v, pre[tot] = last[u], last[u] = tot;}
void dfs1(int u)
{
	if(u == t)	return ;
	fe(i, u)	fa[v] = u, ++son[u], dfs1(v);
}
inline int max(int a, int b){return a > b ? a : b;}
void dfs2(int u)
{
	if(u == t)	return ;
	int mx1 = 0, mx2 = 0;
	fe(i, u)	dfs2(v), w[v] > mx1 ? (mx2 = mx1, mx1 = w[v]) : (mx2 = max(mx2, w[v]));
	w[u] = mx2 + son[u];
}
inline bool check(int c)
{
	int cnt = 0, dep = 0;
	fd(i, m, 2)
	{
		int u = p[i], sc = 0; ++dep; // 设走到这里必须删去的子树的个数为sc.
		fe(j, u)	if(!inp[v])	sc += (w[v] + sum + cnt > c);  

		cnt += sc; sum -= (son[u] - 1);
		if(cnt > dep || cnt > c)	return 0;
	}
	return 1;
}
int main()
{
	freopen("compete.in", "r", stdin);
	freopen("compete.out", "w", stdout);
	n = read(), t = read(), s = read();
	int u, v, ans = -1;
	fo(i, 2, n)	u = read(), v = read(), add(u, v), add(v, u);
	dfs1(s);
	for(int x = t; x != s; x = fa[x])	inp[p[++m] = x] = 1; inp[p[++m] = s] = 1;
	fo(i, 2, m)	sum += (son[p[i]] - 1);
	dfs2(s);
	for(int l = 0, r = n, tmp = sum, mid = (1 + n) >> 1; l <= r; mid = (l + r) >> 1)
	{
		check(mid) ? (r = (ans = mid) - 1) : (l = mid + 1);
		sum = tmp;
	}
	printf("%d", ans);
	return 0;
}
posted @ 2021-01-18 09:32  Martin_MHT  阅读(57)  评论(0)    收藏  举报