• CF1452G Game On Tree


    CF1452G Game On Tree

    题目来源:Codeforces, Educational Codeforces Round 98 (Rated for Div. 2), CF1452G Game On Tree

    题目大意

    题目链接

    Alice 和 Bob 在玩一个游戏。他们有一棵由 (n) 个结点组成的树。一开始,Bob 有 (k) 个卡片,其中第 (i) 个卡片位于结点 (a_i)(注意:所有的结点都是唯一的)。在游戏开始之前,Alice 将在这棵树的一个结点上放置一个卡片。

    这个游戏由一些回合组成。每个回合都将有以下事件发生(完全按照以下顺序):

    1. Alice 可以把她的卡片移到相邻的结点,或者不移动;
    2. 对于 Bob 的每一张卡片,他可以把这张卡片移到相邻的结点,或者不移动。注意:每个卡片的选择都是独立的。

    当 Alice 的卡片与 Bob 的任意一张(或多张)卡片在同一结点时,游戏结束。(Bob 自己的多张卡片可以置于同一结点上,即使它们的初始位置一定是不同的)。

    Alice 希望游戏回合越多越好,Bob则相反。如果某回合中间游戏结束(即 Alice 把卡片移到了有 Bob 卡片的结点上),这回合依然算入总回合数。

    对于每个结点,计算 Alice 一开始将卡片放在该结点时游戏将持续的回合数。

    数据范围:(2leq nleq 2 imes 10^5)(1leq k < n)

    本题题解

    设树上两点 (u,v) 间的距离为 ( ext{dis}(u,v))

    考虑对某个 Alice 的起点 (a) 求答案。先把 (a) 提作树根。

    设 Bob 的所有起点中,离 (u) 最近的点与 (u) 的距离为 ( ext{disBob}(u))

    考虑 Bob 的移动策略,显然他一定让所有点同时向当前 Alice 所在的位置走。

    与此同时 Alice 要避免被 Bob 追到,她会向她能到达的点里,( ext{disBob}(u)) 最大的点走。“她能到达的点”指的是在 Alice 从起点 (a) 走到这个点的过程中,不会被 Bob 逮到。具体来说,一个点 (u) 是“能到达的”,当且仅当 ( ext{dis}(a,u)leq ext{disBob}(u)) 且对于 (a)(u) 路径上除 (u) 外所有点 (v)( ext{dis}(a,v) < ext{disBob}(v))

    对所有点 (uin[1,n]) 预处理出 ( ext{disBob}(u)),可以通过树形 DP + 换根预处理,时间复杂度 (O(n))

    然后对每个 (a) 求答案,只需要从 (a) 出发 dfs 一遍,求出所有“能到达的点” (u)( ext{disBob}(u)) 的最大值。总时间复杂度 (O(n^2))。无法通过本题。

    考虑优化上述过程。将点按 ( ext{disBob}(u)) 从小到大排成一个序列 (p_1,p_2,dots,p_n)(p) 是一个 (1dots n) 的排列且 ( ext{disBob}(p_1)leq ext{disBob}(p_2)leq dotsleq ext{disBob}(p_n))。对每个点 (a),在这个序列上二分答案。问题转化为求 (p_{ ext{mid}+1},dots ,p_{r}) 中,是否存在点 (p_i)(a) “能到达”的。

    单独二分似乎难以操作。我们对所有 (a) 整体二分。具体来说,我们定义一个递归的函数 ( ext{solve}(Q,l,r)) 表示 (Q) 集合里的这些 (a),答案都在 (l,r) 之间。若 (l=r),则直接记下答案,然后返回。否则设 ( ext{mid} = lfloorfrac{l+r}{2} floor)。我们对 (Q) 集合里的所有 (a),判断:集合 (P = {p_{ ext{mid}+1},dots,p_{r}}) 中,是否存在点 (p_i)(a) “能到达”的。

    要一次性判断所有 (a),可以对 (Qcup P) 这些点,建出虚树。然后做树形 DP。设 (f(u)) 表示 (u) 子树里所有 (P) 集合里的点 (v) 中,( ext{dis}(v,u) - ext{disBob}(v)) 的最小值。

    对于根节点 ( ext{root}),如果 (f( ext{root}) < 0),则 ( ext{root}) 的答案在 ([ ext{mid}+1,r]) 中;如果 (f( ext{root}) > 0) 则它的答案在 ([l, ext{mid}]) 中。换根后,即可对所有点分别做出这样的判断。然后将 (Q) 划分为两个集合 (Q_L,Q_R),分别递归调用:( ext{solve}(Q_L,l, ext{mid}))( ext{solve}(Q_R, ext{mid} + 1, r)) 即可。

    需要注意的是,(f( ext{root}) = 0) 的情况是比较特殊的。它能放在 (Q_R),当且仅当存在一个点 (uin P),满足 ( ext{dis}(u, ext{root}) = ext{disBob}(u)),且 (u)( ext{root}) 方向的儿子子树里(也就是以 ( ext{root}) 为根时,(u) 子树以外的所有点),不存在一个 Bob 的起点 (b) 使得 ( ext{dis}(b,u) = ext{disBob}(u))。这也可以通过做一个类似的树形 DP:只要 (u) 的子树外,存在到 (u) 距离等于 ( ext{disBob}(u)) 的 Bob 的起点,我们就不让 (u) 对父亲的 DP 值产生贡献(假装 (u) 不在 (P) 集合里)。然后再换根一次即可。

    发现在递归的过程中,每个节点只会递归 (O(log n)) 层(因为是个二分),每层里要建虚树,所以总时间复杂度 (O(nlog^2n))

    参考代码

    // problem: CF1452G
    #include <bits/stdc++.h>
    using namespace std;
    
    #define pb push_back
    #define mk make_pair
    #define lob lower_bound
    #define upb upper_bound
    #define fi first
    #define se second
    #define SZ(x) ((int)(x).size())
    
    typedef unsigned int uint;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int, int> pii;
    
    template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
    template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }
    
    /* --------------- fast io --------------- */ // begin
    namespace Fread {
    const int SIZE = 1 << 21;
    char buf[SIZE], *S, *T;
    inline char getchar() {
    	if (S == T) {
    		T = (S = buf) + fread(buf, 1, SIZE, stdin);
    		if (S == T) return EOF;
    	}
    	return *S++;
    }
    } // namespace Fread
    namespace Fwrite {
    const int SIZE = 1 << 21;
    char buf[SIZE], *S = buf, *T = buf + SIZE;
    inline void flush() {
    	fwrite(buf, 1, S - buf, stdout);
    	S = buf;
    }
    inline void putchar(char c) {
    	*S++ = c;
    	if (S == T) flush();
    }
    struct NTR {
    	~ NTR() { flush(); }
    }ztr;
    } // namespace Fwrite
    #ifdef ONLINE_JUDGE
    	#define getchar Fread :: getchar
    	#define putchar Fwrite :: putchar
    #endif
    namespace Fastio {
    struct Reader {
    	template<typename T>
    	Reader& operator >> (T& x) {
    		char c = getchar();
    		T f = 1;
    		while (c < '0' || c > '9') {
    			if (c == '-') f = -1;
    			c = getchar();
    		}
    		x = 0;
    		while (c >= '0' && c <= '9') {
    			x = x * 10 + (c - '0');
    			c = getchar();
    		}
    		x *= f;
    		return *this;
    	}
    	Reader& operator >> (char& c) {
    		c = getchar();
    		while (c == '
    ' || c == ' ') c = getchar();
    		return *this;
    	}
    	Reader& operator >> (char* str) {
    		int len = 0;
    		char c = getchar();
    		while (c == '
    ' || c == ' ') c = getchar();
    		while (c != '
    ' && c != ' ') {
    			str[len++] = c;
    			c = getchar();
    		}
    		str[len] = '';
    		return *this;
    	}
    	Reader(){}
    }cin;
    const char endl = '
    ';
    struct Writer {
    	template<typename T>
    	Writer& operator << (T x) {
    		if (x == 0) { putchar('0'); return *this; }
    		if (x < 0) { putchar('-'); x = -x; }
    		static int sta[45];
    		int top = 0;
    		while (x) { sta[++top] = x % 10; x /= 10; }
    		while (top) { putchar(sta[top] + '0'); --top; }
    		return *this;
    	}
    	Writer& operator << (char c) {
    		putchar(c);
    		return *this;
    	}
    	Writer& operator << (char* str) {
    		int cur = 0;
    		while (str[cur]) putchar(str[cur++]);
    		return *this;
    	}
    	Writer& operator << (const char* str) {
    		int cur = 0;
    		while (str[cur]) putchar(str[cur++]);
    		return *this;
    	}
    	Writer(){}
    }cout;
    } // namespace Fastio
    #define cin Fastio :: cin
    #define cout Fastio :: cout
    #define endl Fastio :: endl
    /* --------------- fast io --------------- */ // end
    
    const int MAXN = 2e5;
    const int INF = 1e9;
    
    int n, K, a[MAXN + 5];
    bool mark[MAXN + 5];
    
    struct EDGE { int nxt, to; } edge[MAXN * 2 + 5];
    int head[MAXN + 5], tot;
    inline void add_edge(int u, int v) { edge[++tot].nxt = head[u]; edge[tot].to = v; head[u] = tot; }
    
    namespace TreeChainPartition {
    int fa[MAXN + 5], sz[MAXN + 5], son[MAXN + 5], dep[MAXN + 5];
    void dfs1(int u) {
    	sz[u] = 1;
    	for (int i = head[u]; i; i = edge[i].nxt) {
    		int v = edge[i].to;
    		if (v == fa[u])
    			continue;
    		fa[v] = u;
    		dep[v] = dep[u] + 1;
    		dfs1(v);
    		sz[u] += sz[v];
    		if (!son[u] || sz[v] > sz[son[u]])
    			son[u] = v;
    	}
    }
    int top[MAXN + 5], dfn[MAXN + 5], ofn[MAXN + 5], rev[MAXN + 5], cnt_dfn;
    void dfs2(int u, int t) {
    	top[u] = t;
    	dfn[u] = ++cnt_dfn;
    	rev[cnt_dfn] = u;
    	if (son[u])
    		dfs2(son[u], t);
    	for (int i = head[u]; i; i = edge[i].nxt) {
    		int v = edge[i].to;
    		if (v == fa[u] || v == son[u])
    			continue;
    		dfs2(v, v);
    	}
    	ofn[u] = cnt_dfn;
    }
    int get_lca(int u, int v) {
    	while (top[u] != top[v]) {
    		if (dep[top[u]] < dep[top[v]])
    			swap(u, v);
    		u = fa[top[u]];
    	}
    	return (dep[u] < dep[v]) ? u : v;
    }
    int jump(int u, int _anc) {
    	while (top[u] != top[_anc]) {
    		if (fa[top[u]] == _anc) {
    			return top[u];
    		}
    		u = fa[top[u]];
    	}
    	return rev[dfn[_anc] + 1];
    }
    void init() {
    	dfs1(1);
    	dfs2(1, 1);
    }
    } // namespace TreeChainPartition
    
    using TreeChainPartition :: dep;
    using TreeChainPartition :: dfn;
    using TreeChainPartition :: get_lca;
    using TreeChainPartition :: jump;
    
    int bob_dis[MAXN + 5];
    int ans[MAXN + 5];
    vector<int> vec[MAXN + 5];
    void dfs1(int u, int fa) {
    	vec[u].pb(INF);
    	if (mark[u]) {
    		vec[u].pb(0);
    	}
    	for (int i = head[u]; i; i = edge[i].nxt) {
    		int v = edge[i].to;
    		if (v == fa)
    			continue;
    		dfs1(v, u);
    		if (bob_dis[v] != INF)
    			vec[u].pb(bob_dis[v] + 1);
    	}
    	sort(vec[u].begin(), vec[u].end());
    	bob_dis[u] = vec[u][0];
    }
    int bob_dis_from_fa[MAXN + 5];
    bool flag[MAXN + 5];
    void dfs2(int u, int fa) {
    	if (fa) {
    		int ff;
    		if (bob_dis[u] + 1 == vec[fa][0]) {
    			ff = vec[fa][1];
    		} else {
    			ff = vec[fa][0];
    			flag[u] = 1;
    		}
    		bob_dis_from_fa[u] = ff;
    		
    		if (ff != INF) {
    			vec[u].pb(ff + 1);
    		}
    		sort(vec[u].begin(), vec[u].end());
    		bob_dis[u] = vec[u][0];
    	}
    	for (int i = head[u]; i; i = edge[i].nxt) {
    		int v = edge[i].to;
    		if (v == fa)
    			continue;
    		dfs2(v, u);
    	}
    }
    int nodes_sorted_by_bob_dis[MAXN + 5];
    bool cmp_by_bob_dis(int u, int v) { return bob_dis[u] < bob_dis[v]; }
    
    struct VIREDGE { int nxt, to, w; } vir_edge[MAXN * 2 + 5]; // 虚树
    int vir_head[MAXN + 5], vir_tot;
    int tim[MAXN + 5], TIM;
    inline void vir_add_edge(int u, int v, int w) {
    	if (tim[u] < TIM) {
    		tim[u] = TIM;
    		vir_head[u] = 0; // 懒惰清空
    	}
    	if (tim[v] < TIM) {
    		tim[v] = TIM;
    		vir_head[v] = 0;
    	}
    	vir_edge[++vir_tot].nxt = vir_head[u];
    	vir_edge[vir_tot].to = v;
    	vir_edge[vir_tot].w = w;
    	vir_head[u] = vir_tot;
    }
    int sta[MAXN + 5], top;
    int nodes_sorted_by_dfn[MAXN * 2 + 5], cnt;
    bool cmp_by_dfn(int u, int v) { return dfn[u] < dfn[v]; }
    
    void add_node(int u) {
    	if (!top) {
    		sta[++top] = u;
    		return;
    	}
    	int lca = get_lca(sta[top], u);
    	if (lca == sta[top]) {
    		sta[++top] = u;
    		return;
    	}
    	while (top >= 2 && dep[sta[top - 1]] >= dep[lca]) {
    		vir_add_edge(sta[top - 1], sta[top], dep[sta[top]] - dep[sta[top - 1]]);
    		--top;
    	}
    	
    	if (sta[top] == lca) {
    		sta[++top] = u;
    		return;
    	}
    	vir_add_edge(lca, sta[top], dep[sta[top]] - dep[lca]);
    	sta[top] = lca;
    	sta[++top] = u;
    }
    
    int fw[MAXN + 5];
    int f[MAXN + 5], g[MAXN + 5];
    struct ThreeMin {
    	int a, b, c;
    	void insert(int x) {
    		if (x < a) {
    			c = b;
    			b = a;
    			a = x;
    		} else if (x < b) {
    			c = b;
    			b = x;
    		} else if (x < c) {
    			c = x;
    		}
    	}
    	void erase(int x) {
    		if (x == b) {
    			b = c;
    			c = INF;
    		} else if (x == a) {
    			a = b;
    			b = c;
    			c = INF;
    		}
    	}
    	void init() {
    		a = b = c = INF;
    	}
    	ThreeMin() { init(); }
    };
    
    ThreeMin vf[MAXN + 5], vg[MAXN + 5];
    
    void dfs3(int u) {
    	vf[u].init();
    	vg[u].init();
    	if (mark[u]) {
    		vf[u].insert(-bob_dis[u]);
    		if (bob_dis_from_fa[u] + 1 != bob_dis[u]) {
    			vg[u].insert(-bob_dis[u]);
    		}
    	}
    	for (int i = vir_head[u]; i; i = vir_edge[i].nxt) {
    		int v = vir_edge[i].to;
    		fw[v] = vir_edge[i].w;
    		dfs3(v);
    		if (f[v] != INF)
    			vf[u].insert(f[v] + vir_edge[i].w);
    		if (g[v] != INF)
    			vg[u].insert(g[v] + vir_edge[i].w);
    	}
    	f[u] = vf[u].a;
    	g[u] = vg[u].a;
    }
    void dfs4(int u, int fa) {
    	if (fa) {
    		int ff;
    		if (f[u] + fw[u] == vf[fa].a) {
    			ff = vf[fa].b;
    		} else {
    			ff = vf[fa].a;
    		}
    		if (ff != INF) {
    			vf[u].insert(ff + fw[u]);
    		}
    		f[u] = vf[u].a;
    		
    		int gg;
    		if (g[u] + fw[u] == vg[fa].a) {
    			gg = vg[fa].b;
    		} else {
    			gg = vg[fa].a;
    		}
    		if (mark[fa] && flag[jump(u, fa)]) {
    			ckmin(gg, -bob_dis[fa]);
    		}
    		if (gg != INF) {
    			vg[u].insert(gg + fw[u]);
    		}
    		g[u] = vg[u].a;
    	}
    	if (mark[u] && bob_dis_from_fa[u] + 1 != bob_dis[u]) {
    		vg[u].erase(-bob_dis[u]);
    	}
    	for (int i = vir_head[u]; i; i = vir_edge[i].nxt) {
    		int v = vir_edge[i].to;
    		dfs4(v, u);
    	}
    }
    void solve(const vector<int>& q, int l, int r) {
    	// q 中这些询问的答案在 [l, r] 之间
    	if (!SZ(q))
    		return;
    	if (l == r || bob_dis[nodes_sorted_by_bob_dis[l]] == bob_dis[nodes_sorted_by_bob_dis[r]]) {
    		for (int i = 0; i < SZ(q); ++i) {
    			ans[q[i]] = l;
    		}
    		return;
    	}
    	int mid = (l + r) >> 1;
    	cnt = 0;
    	for (int i = 0; i < SZ(q); ++i) {
    		nodes_sorted_by_dfn[++cnt] = q[i];
    	}
    	for (int i = mid + 1; i <= r; ++i) {
    		nodes_sorted_by_dfn[++cnt] = nodes_sorted_by_bob_dis[i];
    		mark[nodes_sorted_by_bob_dis[i]] = 1;
    	}
    	
    	sort(nodes_sorted_by_dfn + 1, nodes_sorted_by_dfn + cnt + 1, cmp_by_dfn);
    	cnt = unique(nodes_sorted_by_dfn + 1, nodes_sorted_by_dfn + cnt + 1) - (nodes_sorted_by_dfn + 1);
    	
    	top = 0;
    	if (nodes_sorted_by_dfn[1] != 1)
    		add_node(1);
    	for (int i = 1; i <= cnt; ++i)
    		add_node(nodes_sorted_by_dfn[i]);
    	for (int i = 2; i <= top; ++i)
    		vir_add_edge(sta[i - 1], sta[i], dep[sta[i]] - dep[sta[i - 1]]);
    	
    	dfs3(1);
    	dfs4(1, 0);
    	
    	vir_tot = 0;
    	TIM++; // 懒惰清空
    	for (int i = mid + 1; i <= r; ++i) {
    		mark[nodes_sorted_by_bob_dis[i]] = 0;
    	}
    	
    	vector<int> ql, qr;
    	for (int i = 0; i < SZ(q); ++i) {
    		if (f[q[i]] < 0 || (f[q[i]] == 0 && g[q[i]] == 0)) {
    			qr.pb(q[i]);
    		} else {
    			ql.pb(q[i]);
    		}
    	}
    	solve(ql, l, mid);
    	solve(qr, mid + 1, r);
    }
    int main() {
    	cin >> n;
    	for (int i = 1; i < n; ++i) {
    		int u, v;
    		cin >> u >> v;
    		add_edge(u, v);
    		add_edge(v, u);
    	}
    	
    	TreeChainPartition :: init();
    	
    	cin >> K;
    	for (int i = 1; i <= K; ++i) {
    		cin >> a[i];
    		mark[a[i]] = 1;
    	}
    	dfs1(1, 0);
    	dfs2(1, 0);
    	bob_dis_from_fa[1] = INF;
    	// for (int i = 1; i <= n; ++i) cerr << bob_dis[i] << " 
    "[i == n];
    	
    	for (int i = 1; i <= K; ++i) mark[a[i]] = 0;
    	vector<int> q;
    	for (int i = 1; i <= n; ++i) q.pb(i);
    	for (int i = 1; i <= n; ++i) nodes_sorted_by_bob_dis[i] = i;
    	sort(nodes_sorted_by_bob_dis + 1, nodes_sorted_by_bob_dis + n + 1, cmp_by_bob_dis);
    	solve(q, 1, n);
    	for (int i = 1; i <= n; ++i) {
    		cout << bob_dis[nodes_sorted_by_bob_dis[ans[i]]] << " 
    "[i == n];
    	}
    	return 0;
    }
    
  • 相关阅读:
    Zookeeper
    激活函数
    线程池
    用rand()和srand()产生伪随机数的方法总结 (转贴)
    连接SDE数据库代码
    ProgressBar
    ArcEngine+OpenGL之二系统平台搭建
    我所知道的ArcObjects开发(转)
    oracle wm_concat函数 乱码
    修改32位的AutoCAD2012,使其能在64位系统上安装
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/14038709.html
Copyright © 2020-2023  润新知