• CF1416D Graph and Queries


    题目来源:Codeforces,CF,Codeforces Round #673 (Div. 1),CF1416,D题,CF1416D Graph and Queries。

    题目大意

    题目链接

    给你一张 (n) 个点 (m) 条边的无向图。点有点权,每个点的点权 (p_i)(1)(n) 的正整数且互不相同。

    依次进行 (q) 次操作,每次操作是如下两种之一:

    • (1 v):查询 (v) 在图上能到达的所有节点里(包括 (v) 自己),(p_u) 最大的点 (u)。输出 (p_u)。并将 (p_u) 置为 (0)
    • (2 i):删除第 (i) 条边。

    特别地,当所有 (v) 能到达的点点权都为 (0) 时,(u) 是未定义的。但此时选任意一个节点作为 (u) 答案都是 (0),所以你只需要输出 (0) 即可。

    数据范围:(1leq nleq 2cdot10^5)(1leq mleq 3cdot 10^5)(1leq qleq 5cdot10^5)

    本题题解

    不会删边。所以考虑离线,按时间倒序进行操作,删边变成加边

    但是遇到的麻烦是,操作 (1) 是正序进行的,如果我们倒序操作,就不知道当前哪些点 (p_u = 0) 了。

    解决方法是,先倒序遍历一遍所有操作,按“加边”的顺序,建出重构树。重构树优美的性质是,对任意一个节点 (v),在某个时刻之前和它连通的节点,恰好是重构树上 (v) 的某个祖先的子树。并且我们可以通过树上倍增,在 (O(log n)) 的时间内找到这个祖先。

    建出重构树后,我们回到正向的时间线。按正序处理所有询问(操作 (1))。前面说过,在某个时刻和 (v) 连通的节点,是 (v) 某个祖先的子树。先倍增找到这个祖先。它的子树是 dfs 序上连续的一段。我们预处理出重构树的 dfs 序,那么问题转化为求区间最大值,支持单点修改。可以用线段树维护。

    时间复杂度 (O((n+m+q)log n))

    总结

    因为 LCT 是垃圾,所以我们不考虑它。那么对于删边操作,其实我们能使用的手段是很有限的。

    例如,一个经典的思路是把询问分块。只维护进入本块之前的连通性,对本块内的贡献暴力计算。不过我顺着此思路思考本题并没有得到很好的结果。

    另一个思路就是倒序、删边变加边了。但是本题的特殊之处在于,它的另一种操作(操作 (1))强烈依赖正序的时间线。所以我们不是简单的倒序操作,而是先倒序预处理出一个数据结构,然后用这个数据结构来正序地操作和回答询问。

    顺便扯一句,这道题让我想到了最近热映的一部电影:《信条》,讲的就是一条正序时间线、一条倒序时间线,交织在一起,发生的事情。而本题里的“重构树”这个工具,就好像是《信条》里未来人传送给现代人的“逆向武器”和“算法”。

    参考代码

    实际提交时,建议加上读入优化,详见本博客公告。

    // problem: CF1416D
    #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); }
    
    const int MAXN = 2e5, MAXM = 3e5, MAXQ = 5e5;
    const int LOG = 18;
    
    int n, m, q, val[MAXN + 5];
    bool init_in_map[MAXM + 5];
    struct Edge_t {
    	int u, v;
    };
    struct Query_t {
    	int op, x;
    };
    
    Edge_t edges[MAXM + 5], edges_sorted[MAXM + 5];
    Query_t queries[MAXQ + 5];
    int cnt_e, tim[MAXQ + 5];
    
    int fa[MAXN * 2 + 5], cnt_node;
    inline int get_fa(int u) { return (u == fa[u]) ? u : (fa[u] = get_fa(fa[u])); }
    inline int new_node() {
    	++cnt_node;
    	fa[cnt_node] = cnt_node;
    	return cnt_node;
    }
    
    struct EDGE { int nxt, to; } edge[MAXN * 2 + 5];
    int head[MAXN * 2 + 5], tot;
    inline void add_edge(int u, int v) { edge[++tot].nxt = head[u]; edge[tot].to = v; head[u] = tot; }
    
    int node_tim[MAXN * 2 + 5]; // 树上这个节点代表的加边时间
    int dfn[MAXN + 5], rev[MAXN + 5], min_dfn[MAXN * 2 + 5], max_dfn[MAXN * 2 + 5], cnt_dfn;
    int anc[MAXN * 2 + 5][LOG + 1];
    
    void dfs(int u) {
    	for(int i = 1; i <= LOG; ++i) {
    		anc[u][i] = anc[anc[u][i - 1]][i - 1];
    	}
    	bool is_leaf = 1;
    	min_dfn[u] = n * 2;
    	for(int i = head[u]; i; i = edge[i].nxt) {
    		int v = edge[i].to;
    		is_leaf = 0;
    		anc[v][0] = u;
    		dfs(v);
    		ckmin(min_dfn[u], min_dfn[v]);
    		ckmax(max_dfn[u], max_dfn[v]);
    	}
    	if(is_leaf) {
    		++cnt_dfn;
    		min_dfn[u] = max_dfn[u] = dfn[u] = cnt_dfn;
    		rev[cnt_dfn] = u;
    	}
    }
    int get_anc(int u, int t) {
    	// u 的最高的 node_tim <= t 的祖先
    	int v = u;
    	for(int i = LOG; i >= 0; --i) {
    		if(anc[v][i] && node_tim[anc[v][i]] <= t) {
    			v = anc[v][i];
    		}
    	}
    	return v;
    }
    
    struct SegmentTree {
    	int mx[MAXN * 4 + 5], mx_pos[MAXN * 4 + 5];
    	void push_up(int p) {
    		if(mx[p << 1] > mx[p << 1 | 1]) {
    			mx[p] = mx[p << 1];
    			mx_pos[p] = mx_pos[p << 1];
    		} else {
    			mx[p] = mx[p << 1 | 1];
    			mx_pos[p] = mx_pos[p << 1 | 1];
    		}
    	}
    	void build(int p, int l, int r) {
    		if(l == r) {
    			mx[p] = val[rev[l]];
    			mx_pos[p] = l;
    			return;
    		}
    		int mid = (l + r) >> 1;
    		build(p << 1, l, mid);
    		build(p << 1 | 1, mid + 1, r);
    		push_up(p);
    	}
    	void point_modify(int p, int l, int r, int pos) {
    		if(l == r) {
    			mx[p] = 0;
    			return;
    		}
    		int mid = (l + r) >> 1;
    		if(pos <= mid) {
    			point_modify(p << 1, l, mid, pos);
    		} else {
    			point_modify(p << 1 | 1, mid + 1, r, pos);
    		}
    		push_up(p);
    	}
    	pii query(int p, int l, int r, int ql, int qr) {
    		if(ql <= l && qr >= r) {
    			return make_pair(mx[p], mx_pos[p]);
    		}
    		int mid = (l + r) >> 1;
    		pii res = mk(0, 0);
    		if(ql <= mid) {
    			res = query(p << 1, l, mid, ql, qr);
    		}
    		if(qr > mid) {
    			ckmax(res, query(p << 1 | 1, mid + 1, r, ql, qr));
    		}
    		return res;
    	}
    	SegmentTree() {}
    }SegT;
    
    int main() {
    	cin >> n >> m >> q;
    	for(int i = 1; i <= n; ++i) {
    		cin >> val[i];
    	}
    	for(int i = 1; i <= m; ++i) {
    		cin >> edges[i].u >> edges[i].v;
    		init_in_map[i] = 1;
    	}
    	for(int i = 1; i <= q; ++i) {
    		cin >> queries[i].op >> queries[i].x;
    		if(queries[i].op == 2) {
    			assert(init_in_map[queries[i].x] == 1);
    			init_in_map[queries[i].x] = 0;
    		}
    	}
    	for(int i = 1; i <= m; ++i) {
    		if(init_in_map[i]) {
    			++cnt_e;
    			edges_sorted[cnt_e] = edges[i];
    		}
    	}
    	for(int i = q; i >= 1; --i) {
    		if(queries[i].op == 2) {
    			++cnt_e;
    			edges_sorted[cnt_e] = edges[queries[i].x];
    		} else {
    			tim[i] = cnt_e;
    		}
    	}
    	
    	assert(cnt_e == m);
    	for(int i = 1; i <= n; ++i) {
    		new_node();
    	}
    	for(int i = 1; i <= m; ++i) {
    		int u = get_fa(edges_sorted[i].u);
    		int v = get_fa(edges_sorted[i].v);
    		if(u != v) {
    			int par = new_node();
    			fa[u] = fa[v] = par;
    			node_tim[par] = i;
    			add_edge(par, u);
    			add_edge(par, v);
    		}
    	}
    	for(int i = 1; i <= cnt_node; ++i) {
    		if(get_fa(i) == i) {
    			dfs(i);
    		}
    	}
    	assert(cnt_dfn == n);
    	SegT.build(1, 1, n);
    	for(int i = 1; i <= q; ++i) {
    		if(queries[i].op == 1) {
    			int u = queries[i].x;
    			int v = get_anc(u, tim[i]);
    			
    			pii qres = SegT.query(1, 1, n, min_dfn[v], max_dfn[v]);
    			cout << qres.fi << endl;
    			if(qres.fi) {
    				SegT.point_modify(1, 1, n, qres.se);
    			}
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    02.v-on的事件修饰符
    01.Vue的系统指令
    00-Vue的介绍和vue-cli
    vs code快捷键
    分库分表之后,主键的处理方法
    动态扩容分库分表
    前端web通过flask操作数据库-增删改查
    mysql组复制集群简介
    vsftp进阶-锁定目录
    kvm克隆
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/13753738.html
Copyright © 2020-2023  润新知