[AOJ 2170]Marked Ancestor[并查集][离线][路径压缩]or[线段树]

题目链接: [AOJ 2170]Marked Ancestor[并查集][离线][路径压缩]or[线段树]

题意分析:

结点1为根结点,初始时已经染过颜色。给出N - 1行,第i行代表第i + 1个结点的父亲结点是哪个结点。

现在给出最多1e5个结点,1e5个查询。查询操作分两种,Q X代表查询结点X的最近的被染色的父或者祖先结点被染色的编号,M X代表对结点X染色。

问:所有查询完之后,Q查询的编号值之和为多少?

解题思路:

最坏情况下为整棵树成为一条链,单纯使用并查集查询复杂度最坏1e10。然而单纯使用并查集也能过,这题估计数据水。(或者谁告诉我下,1e10八秒是无压力跑的,那我也就没什么意见了。)

正解应该是将查询存下来,存下每次Q操作的查询时间和查询结点,用qt和qv数组存储。另外设置mark数组存储这个结点被染色的最快时间。然后我们倒着进行查询操作,当当前查询的结点最早被染色时间小于当前时间时,就可以返回这个结点了,否则进行路径压缩(进入否则,说明这个结点的最快被染色时间大于查询时间,而我们是倒着进行操作的,说明后继的查询时间都比当前的查询时间小,不管怎么样,这个结点都不会被染色了,无用,直接拿来压缩掉)

还有一种解法就是使用线段树来解这道题,利用dfs序来维护。具体是队友做出来了,我也不大懂怎么搞= =,膜拜ORZ 具体做法见下↓

个人感受:

1e10啊!!!!第一次做的时候不管三七二十一就最简单的find函数去上交,竟然A了。

想想这个复杂度不对啊,查题解各种涨姿势ORZ ORZ ORZ 当时现场赛的同学一定是崩溃的= =

具体代码如下:

并查集君:

#include<cstdio>
#include<iostream>
#define ll long long
using namespace std;

const int INF = 0x7f7f7f7f;
const int MAXN = 1e5 + 111;

int p[MAXN];
int qt[MAXN], qv[MAXN], mark[MAXN];
int t;

int find(int x) {
    return mark[x] < t ? x : p[x] = find(p[x]); // 小于则说明在查询之前已经染过颜色
}

int main()
{
    int n, q;
    while (~scanf("%d%d", &n, &q) && (n | q)) {
        for (int i = 2; i <= n; ++i) {
            scanf("%d", p + i);
            mark[i] = INF;
        }

        int cnt = 0, x;
        char op[2];
        for (int i = 1; i <= q; ++i) {
            scanf("%s%d", op, &x);
            if (op[0] == 'M') mark[x] = min(mark[x], i); // 记录最早染色时间
            else {
                qt[cnt] = i;
                qv[cnt++] = x;
            }
        }

        ll ans = 0;
        while (cnt --) {
            t = qt[cnt]; // 查询发生的时间
            ans += find(qv[cnt]);
        }
        printf("%lld\n", ans);
    }
    return 0;
}

线段树君(队友代码):

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <string>
#include <queue>
#include <cstdlib>
#include <algorithm>
#include <stack>
#include <map>
#include <queue>
#include <vector>

using namespace std;
const int maxn = 1e5+100;
const int INF = 0x3f3f3f3f;
#define pr(x)      // cout << #x << " = " << x << " ";
#define prln(x)    // cout << #x << " = " << x <<endl;
#define ll long long
int head[maxn], nxt[maxn], to[maxn], dfsn, cnt, id[maxn], r[maxn], _n, sum[maxn<<2], dep[maxn];
void addedge(int u, int v) {
	nxt[cnt] = head[u];
	head[u] = cnt;
	to[cnt++] = v;
}
void init(int n) {
	cnt = dfsn = 0;
	_n = 1;
	while(_n < n) _n = _n*2;
	int _nn = _n*2;
	for(int i = 0; i <= _nn; ++i) sum[i] = -1;
	for(int i = 0; i <= n;++i) {
		head[i] =-1;
	}
}

void dfstree(int fa,int u) {
	id[u] = ++dfsn;
	dep[u] = dep[fa]+1;
	for(int i = head[u]; ~i; i = nxt[i]){
		dfstree(u,to[i]);
	}
	r[u] = dfsn;
}
inline void getans(int& ans, const int& v2){
	if(ans == -1 || dep[ans] < dep[v2]) ans = v2;
}
void pushdown(int rt) {
	if(sum[rt] != -1) {
		getans(sum[rt<<1], sum[rt]);
		getans(sum[rt<<1|1],sum[rt]);
	}
}
void update(int rt, int l, int r, int ql, int qr, int v) {
	if(ql <= l && r <= qr) {
		getans(sum[rt], v);
		return;
	}
	pushdown(rt);
	int m = l + r >> 1;
	if(m >= ql) update(rt<<1, l, m, ql, qr, v);
	if(m < qr) update(rt<<1|1, m+1, r, ql, qr, v);
}
int query(int rt) {
	rt += _n-1;
	int ans = 0;
	while(rt>=1) {
		getans(ans,sum[rt]);
		rt = rt>>1;
	}
	return ans;
}
int main(){
#ifdef LOCAL
    freopen("C:\\Users\\Administrator\\Desktop\\in.txt","r",stdin);
  //freopen("C:\\Users\\Administrator\\Desktop\\out.txt","w",stdout);
 #endif
    int n, m, x;
    char op[10];
    while(cin >> n >> m && (n||m)) {
    	ll ans = 0;
    	init(n);
    	for(int i = 2; i <= n; ++i) {
    		scanf("%d", &x);
    		addedge(x,i);
    	}
    	dep[0] = 0;
    	dfstree(0,1);
    	update(1, 1, _n, id[1], r[1], 1);
    	for(int i = 0; i < m; ++i) {
    		scanf("%s%d", op, &x);
    		if(op[0] == 'M')	update(1, 1, _n, id[x], r[x], x);
    		else	ans += query(id[x]);
    	}
    	printf("%lld\n", ans);
    }
    return 0;
}


你可能感兴趣的:(树,并查集)