zoukankan      html  css  js  c++  java
  • @loj


    @description@

    在日本的茨城县内共有 N 个城市和 M 条道路。这些城市是根据人口数量的升序排列的,依次编号为 0 到 N-1。每条道路连接两个不同的城市,并且可以双向通行。由这些道路,你能从任意一个城市到另外任意一个城市。

    你计划了 Q 个行程,这些行程分别编号为 0 至 Q-1。第 i(0 <= i <= Q - 1)个行程是从城市 Si 到城市 Ei。

    你是一个狼人。你有两种形态:人形和狼形。在每个行程开始的时候,你是人形。在每个行程结束的时候,你必须是狼形。在行程中,你必须要变身(从人形变成狼形)恰好一次,而且只能在某个城市内(包括可能是在 Si 或 Ei 内)变身。

    狼人的生活并不容易。当你是人形时,你必须避开人少的城市,而当你是狼形时,你必须避开人多的城市。对于每一次行程 i(0 <= i <= Q - 1),都有两个阈值 Li 和 Ri(0 <= Li <= Ri <= N - 1),用以表示哪些城市必须要避开。
    准确地说,当你是人形时,你必须避开城市 0, 1, ..., Li - 1;而当你是狼形时,则必须避开城市 Ri + 1, Ri + 2, ..., N - 1。
    这就是说,在行程 i 中,你必须在城市 Li, Li+1, ..., Ri 中的其中一个城市内变身。

    你的任务是,对每一次行程,判定是否有可能在满足上述限制的前提下,由城市 Si 走到城市 Ei。你的路线可以有任意长度。

    输入格式
    你需要实现下面的函数:

    int[] check_validity(int N, int[] X, int[] Y, int[] S, int[] E, int[] L, int[] R)

    N:城市的数量
    X 和 Y:两个长度为 M 的数组。对于每个 j(0 <= j <= M - 1),城市 X[j] 都有道路直接连到城市 Y[j]。
    S, E, L, 及 R:均为长度为 Q 的数组,以表示行程。

    数据范围与提示
    2 <= N <= 200000, N - 1 <= M <= 400000, 1 <= Q <= 200000。
    0 <= Li <= Si <= N - 1, 0 <= Ei <= Ri <= N - 1, Si ≠ Ei, Li <= Ri。
    保证没有重边自环,保证图连通。

    @solution@

    等价的题意:
    从 S 出发只经过 L ~ N-1 的点能够到达的点集,与从 E 出发只经过 0 ~ R 的点能够到达的点集,两个集合是否有交集。

    现先考虑从 E 出发只经过 0 ~ R 的点能够到达的点。
    如果从点 0 到点 N - 1 依次加入它们向前连的边,并用类似于可持久化并查集的东西维护,只要调用第 R 个版本的并查集就可以得到 E 能到达的点。
    可持久化并查集?有没有想起 NOI2018 的 D1T1?是否对于这道题我们也可以转成 kruskal 重构树来做?

    考虑对于所有边,记边权为 min{它连接的两个端点的编号},则只经过 0 ~ R 的点等价于只经过边权 <= R 的边。
    于是一个点能够到达的点集,通过倍增可以找到 kruskal 重构树中对应的子树,子树内的所有点都是可到达的。
    从 S 出发只经过 L ~ N-1 的点能够到达的点集可以类比着建另一棵树。

    通过 dfs 序,我们可以把问题转化为:
    是否存在一个 (dfn1[x], dfn2[x]),使得 a <= dfn1[x] <= b, c <= dfn2[x] <= d。
    经典的二维偏序问题,相当于统计这个二维区域的点数。

    懒得动脑子为了强化代码能力打了个可持久化线段树。
    时间复杂度 O(nlogn)。

    @accepted code@

    #include "werewolf.h"
    #include <vector>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    #define vi vector<int>
    #define pb push_back
    const int MAXN = 200000;
    const int MAXQ = 200000;
    const int MAXM = 400000;
    int N, M, Q;
    struct Graph{
    	bool type;
    	struct edge{
    		int to; edge *nxt;
    	}edges[2*MAXN + 5], *adj[2*MAXN + 5], *ecnt;
    	int v[2*MAXN + 5], fa[20][2*MAXN + 5];
    	Graph(int p) {ecnt = edges, type = p;}
    	void addedge(int u, int v) {
    		edge *p = (++ecnt);
    		p->to = v, p->nxt = adj[u], adj[u] = p;
    //		printf("! %d %d
    ", u, v);
    	}
    	void dfs(int x, int f) {
    		fa[0][x] = f;
    		for(int i=1;i<20;i++)
    			fa[i][x] = fa[i-1][fa[i-1][x]];
    		for(edge *p=adj[x];p;p=p->nxt)
    			dfs(p->to, x);
    	}
    	void build(int x) {v[0] = type ? (MAXN + 5) : 0; dfs(x, 0);}
    	int query(int x, int k) {
    		for(int i=19;i>=0;i--)
    			if( type ) {
    				if( v[fa[i][x]] <= k )
    					x = fa[i][x];
    			}
    			else {
    				if( v[fa[i][x]] >= k )
    					x = fa[i][x];
    			}
    		return x;
    	}
    }G1(0), G2(1);
    int fa[2*MAXN + 5];
    int find(int x) {
    	return fa[x] = (fa[x] == x ? x : find(fa[x]));
    }
    vi e[MAXN + 5];
    void build1() {
    	int cnt = N;
    	for(int i=1;i<(N<<1);i++) fa[i] = i;
    	for(int i=N;i>=1;i--) {
    		G1.v[i] = i;
    		for(int j=0;j<(int)e[i].size();j++)
    			if( e[i][j] > i ) {
    				int p = e[i][j], f = find(i), g = find(p);
    				if( f != g ) {
    					int x = (++cnt);
    					G1.addedge(x, f), G1.addedge(x, g);
    					fa[f] = fa[g] = x;
    					G1.v[x] = i;
    				}
    			}
    	}
    	G1.build((N<<1)-1);
    }
    void build2() {
    	int cnt = N;
    	for(int i=1;i<(N<<1);i++) fa[i] = i;
    	for(int i=1;i<=N;i++) {
    		G2.v[i] = i;
    		for(int j=0;j<(int)e[i].size();j++)
    			if( e[i][j] < i ) {
    				int p = e[i][j], f = find(i), g = find(p);
    				if( f != g ) {
    					int x = (++cnt);
    					G2.addedge(x, f), G2.addedge(x, g);
    					fa[f] = fa[g] = x;
    					G2.v[x] = i;
    				}
    			}
    	}
    	G2.build((N<<1)-1);
    }
    struct segtree{
    	struct node{
    		int cnt; node *ch[2];
    	}pl[20*MAXN + 5], *NIL, *ncnt;
    	segtree() {
    		NIL = ncnt = pl;
    		NIL->ch[0] = NIL->ch[1] = NIL;
    		NIL->cnt = 0;
    	}
    	node *insert(node *pre, int l, int r, int ps) {
    		node *p = (++ncnt); (*p) = (*pre); p->cnt++;
    		if( l == r ) return p;
    		int mid = (l + r) >> 1;
    		if( ps <= mid )
    			p->ch[0] = insert(pre->ch[0], l, mid, ps);
    		else p->ch[1] = insert(pre->ch[1], mid + 1, r, ps);
    		return p;
    	}
    	int query(node *L, node *R, int l, int r, int ql, int qr) {
    		if( ql > r || qr < l ) return 0;
    		if( ql <= l && r <= qr ) return R->cnt - L->cnt;
    		int mid = (l + r) >> 1;
    		return query(L->ch[0], R->ch[0], l, mid, ql, qr) + query(L->ch[1], R->ch[1], mid + 1, r, ql, qr);
    	}
    }T;
    segtree::node *rt[MAXN + 5];
    int tid[2][MAXN + 5], dfn[2][MAXN + 5], dcnt[2];
    int le[2][2*MAXN + 5], ri[2][2*MAXN + 5];
    void dfs(const Graph &G, int x, int t) {
    	if( x <= N ) {
    		dfn[t][le[t][x] = ri[t][x] = tid[t][x] = (++dcnt[t])] = x;
    	}
    	else {
    		le[t][x] = dcnt[t] + 1;
    		for(Graph::edge *p=G.adj[x];p;p=p->nxt)
    			dfs(G, p->to, t);
    		ri[t][x] = dcnt[t];
    	}
    }
    void get() {
    	build1();
    	build2();
    	dfs(G1, (N<<1)-1, 0), dfs(G2, (N<<1)-1, 1);
    	rt[0] = T.NIL;
    	for(int i=1;i<=N;i++)
    		rt[i] = T.insert(rt[i-1], 1, N, tid[1][dfn[0][i]]);
    }
    bool query(int s, int e, int l, int r) {
    	int x = G1.query(s, l), y = G2.query(e, r);
    	return T.query(rt[le[0][x]-1], rt[ri[0][x]], 1, N, le[1][y], ri[1][y]);
    }
    vi check_validity(int _N, vi X, vi Y, vi S, vi E, vi L, vi R) {
    	N = _N, Q = S.size(), M = X.size();
    	for(int i=0;i<M;i++)
    		e[X[i]+1].pb(Y[i]+1), e[Y[i]+1].pb(X[i]+1);
    	get(); vi A(Q);
    	for(int i=0;i<Q;i++)
    		A[i] = query(S[i]+1, E[i]+1, L[i]+1, R[i]+1);
    	return A;
    }
    /*
    S -> L ~ N (L <= S)
    E -> 1 ~ R (E <= R)
    */
    

    @details@

    对于 kruskal 重构树,其实我更愿意将其看作 “可持久化并查集”。
    因为它的作用就是将某时刻 t 之前的加边操作执行后,图连通的情况。

    但是某种意义上来说,kruskal 重构树的思想更类似于点分树之类的东西。即用数据结构再现某一算法的过程。

    不管怎么说,会用就行。
    另外,比狠人还多一点——是个狼人。

  • 相关阅读:
    WPF资源字典使用
    忍住你的痛苦
    WPF附加属性
    一致性Hash算法详解
    登录注册
    REST
    资源访问
    参与Bean的生命周期
    sqlserver中查询横表变竖表的sql语句简析
    C#跨线程修改控件——从MSIL和汇编看Invoke, 多线程, 事件与事件委托
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11778293.html
Copyright © 2011-2022 走看看