2021牛客暑期多校训练营7 F-xay loves trees

没有传送门。


题面大意:给你两棵树,根都是1号节点。让你找一个最大的节点集合,满足:
1.在第一棵树上,集合中的节点是相连的,且任意连个节点之间是祖孙关系。
2.在第二棵树上,任意两个节点直接都不是祖孙关系。


这题当时想出来了,结果有一个数组忘清空,先WA后RE,到最后也没查出来。


首先根据第一个条件,这些点一定某一个节点到根的链的一段。那么一种思路是处理出每一个节点\(u\)往上最高能往上走几步(即\(ans[u]\)),最后取最大值。而且对于一条边\((u,v)\)\(u\)\(v\)的父亲),必有\(ans[v] \leqslant ans[u] + 1\).

那对于一个节点,如何求出\(ans[u]\)呢?我现在会两种做法:


第一种做法:
答案显然具有单调性,因此我们对于每一个节点,二分其能向上走的步数。那么对于一个二分值\(mid\),应该如何检验?即怎么判断这些点互相不是祖孙关系。其实就是将第二棵树的dfs序求出来,那么每一个点的dfs序区间就是\([dfn[u], dfn[u] + siz[u] - 1]\),只要判断这些区间不相交即可。

如果是到根的路径,可以用线段树实现区间修改和查询区间和是否为\(0\);现在改成了一段,那么就能想到用主席树。虽然主席树不支持区间修改,但是可以这么解决:建立两棵主席树,一棵叫“祖先树”,一棵叫“儿子树”。

祖先树专门用来判断\(u\)是否为某些点的祖先,那么只要查询\([dfn[u], dfn[u] +siz[u] - 1]\)这段区间和是否为\(0\)。而修改恰好是单点修改,将\(dfn[u]\)标记成\(1\)即可。

儿子树专门用来判断\(u\)是否为某些点的儿子,如果单点查询\(dfn[u]\)的值是否为\(0\),那修改就要区间修改。因此我们修改的时候用差分,查询的时候查询前缀和就行了!

这样两棵主席树都是区间(前缀)查询和单点修改,主席树就能胜任了。时间复杂度\(O(nlog^2n)\),能过。(比赛的时候是倍增的数组忘清空了……)


第二种做法:
上面提到主席树无法区间修改,是因为标记无法下传(会下传到被继承的原来的节点)。但是如果用“标记永久化”就可以实现了。

标记永久化好久没写,完全忘掉了。其思路就是将改变的值的贡献直接算到区间里,在递归边界时打标记。但是标记不下传,而是在查询的时候,算上标记的贡献。

对于这道题,我们可以用标记永久化维护覆盖某一个区间的最深的点的深度,那么这个深度之下必然没有点可以覆盖这一dfs序的区间。因此我们建立一棵树,查询的时候就查询该dfs序区间被覆盖的最大深度,修改的时候就进行区间修改,更新最大值。

而且该算法不用二分,因为我们需要的就是前缀查询,标记永久化刚好可以胜任。这样时间复杂度就是\(O(nlogn)\)了。


一下给出两份代码:
第一种做法:

#include<bits/stdc++.h>
using namespace std;
#define enter puts("")
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
#define forE(i, x, y) for(int i = head[x], y; ~i && (y = e[i].to); i = e[i].nxt)
typedef long long ll;
const int maxn = 3e5 + 500;
const int maxt = 1.2e7 + 5;
const int N = 18;
ll read() {ll x; scanf("%lld", &x); return x;}
void write(ll x) {printf("%lld", x);}
 
int n;
struct Edge
{
    int nxt, to;
}e[maxn << 1];
int head[maxn], ecnt = -1;
In void addEdge(int x, int y)
{
    e[++ecnt] = (Edge){head[x], y};
    head[x] = ecnt;
}
Edge e2[maxn << 1];
int head2[maxn], ecnt2 = -1;
In void addEdge2(int x, int y)
{
    e2[++ecnt2] = (Edge){head2[x], y};
    head2[x] = ecnt2;
}
 
int dfsx[maxn], siz[maxn], cnt = 0;
In void dfs2(int now, int _f)
{
    siz[now] = 1;
    dfsx[now] = ++cnt;
    for(int i = head2[now], v; ~i && (v = e2[i].to); i = e2[i].nxt)
    {
        if(v == _f) continue;
        dfs2(v, now);
        siz[now] += siz[v];
    }
}
 
const int P = maxn - 20;
struct Tree
{
    int ls = 0, rs = 0, sum = 0;
    In void init() {ls = rs = sum = 0;}
};
struct trees
{
    Tree t[maxt];
    int root[maxn << 1], cnt;
    In void init() {Mem(root, 0), cnt = 0;}
    In void insert(int old, int& now, int l, int r, int x, int d)
    {
        t[now = ++cnt] = t[old];
        if(x < l || x > r) return;
        t[now].sum += d;
        if(l == r) return;
        int mid = (l + r) >> 1;
        if(x <= mid) insert(t[old].ls, t[now].ls, l, mid, x, d);
        else insert(t[old].rs, t[now].rs, mid + 1, r, x, d);
    }
    In int query(int old, int now, int l, int r, int L, int R)
    {
        if(!old && !now) return 0;
        if(l == L && r == R) return t[now].sum - t[old].sum;
        int mid = (l + r) >> 1;
        if(R <= mid) return query(t[old].ls, t[now].ls, l, mid, L, R);
        else if(L > mid) return query(t[old].rs, t[now].rs, mid + 1, r, L, R);
        else return query(t[old].ls, t[now].ls, l, mid, L, mid) + query(t[old].rs, t[now].rs, mid + 1, r, mid + 1, R);
    }
}tA, tS;
 
int ha[maxn], dep[maxn], fa[N + 2][maxn];
In int calc(int x, int len)
{
    int sta = x;
    for(int i = ha[len]; i >= 0; --i)
    {
        int y = fa[i][x], z = fa[0][y];
        if(!y) continue;
        int tp1 = tA.query(tA.root[z], tA.root[fa[0][sta]], 1, n, dfsx[sta], dfsx[sta] + siz[sta] - 1);
        int tp2 = tS.query(tS.root[z], tS.root[fa[0][sta]], 1, n, 1, dfsx[sta]);
        if(!tp1 && !tp2) x = y;
    }
    return dep[sta] - dep[x];
}
 
int ans[maxn];
In void dfs1(int now, int _f)
{
    for(int i = 1; (1 << i) <= dep[now]; ++i)
        fa[i][now] = fa[i - 1][fa[i - 1][now]];
	ans[now] = min(ans[_f] + 1, calc(now, ans[_f] + 1));
    tA.insert(tA.root[_f], tA.root[now], 1, n, dfsx[now], 1);
    tS.insert(tS.root[_f], tS.root[P], 1, n, dfsx[now], 1);
    tS.insert(tS.root[P], tS.root[now], 1, n, dfsx[now] + siz[now], -1);
    forE(i, now, v)
    {
        if(v == _f) continue;
        dep[v] = dep[now] + 1;
        fa[0][v] = now;
        dfs1(v, now);
    }
}
 
In void init()
{
	Mem(fa, 0);
    Mem(head, -1), ecnt = -1;
    Mem(head2, -1), ecnt2 = -1;
    cnt = 0;
    tA.init(), tS.init();
}
 
int main()
{
    int T = read();
    while(T--)
    {
        init();
        n = read();
        for(int i = 1; i < n; ++i)
        {
            int x = read(), y = read();
            addEdge(x, y), addEdge(y, x);
        }
        for(int i = 1; i < n; ++i)
        {
            int x = read(), y = read();
            addEdge2(x, y), addEdge2(y, x);
        }
        dfs2(1, 0);
        for(int i = 2; i <= n; ++i) ha[i] = ha[i >> 1] + 1;
        dep[1] = 1, dfs1(1, 0);
        int Max = 0;
        for(int i = 1; i <= n; ++i) Max = max(Max, ans[i]);
        write(Max + 1), enter;
    }
    return 0;
}

比较长,还慢,还是看第二种做法吧。

#include<bits/stdc++.h>
using namespace std;
#define enter puts("") 
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const int maxn = 3e5 + 5;
const int maxt = 6e6 + 5;
In ll read()
{
	ll ans = 0;
	char ch = getchar(), las = ' ';
	while(!isdigit(ch)) las = ch, ch = getchar();
	while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
	if(las == '-') ans = -ans;
	return ans;
}
In void write(ll x)
{
	if(x < 0) x = -x, putchar('-');
	if(x >= 10) write(x / 10);
	putchar(x % 10 + '0');
}

int n;
#define pb push_back
vector<int> v1[maxn], v2[maxn];

int dfsx[maxn], siz[maxn], cnt = 0;
In void dfs2(int now, int _f)
{
	siz[now] = 1, dfsx[now] = ++cnt;
	for(auto v : v2[now])
	{
		if(v == _f) continue;
		dfs2(v, now);
		siz[now] += siz[v];
	}
}

struct Tree
{
	int ls, rs, lzy, Max;
}t[maxt];
int root[maxn], tcnt = 0;
In void insert(int old, int& now, int l, int r, int L, int R, int d)
{
	t[now = ++tcnt] = t[old];
	t[now].Max = max(t[now].Max, d);
	if(l == L && r == R) return (void)(t[now].lzy = max(t[now].lzy, d));
	int mid = (l + r) >> 1;
	if(R <= mid) insert(t[old].ls, t[now].ls, l, mid, L, R, d);
	else if(L > mid) insert(t[old].rs, t[now].rs, mid + 1, r, L, R, d);
	else insert(t[old].ls, t[now].ls, l, mid, L, mid, d), insert(t[old].rs, t[now].rs, mid + 1, r, mid + 1, R, d);
}
In int query(int now, int l, int r, int L, int R)
{
	if(l == L && r == R) return t[now].Max;
	int ret, mid = (l + r) >> 1;
	if(R <= mid) ret = query(t[now].ls, l, mid, L, R);
	else if(L > mid) ret = query(t[now].rs, mid + 1, r, L, R);
	else ret = max(query(t[now].ls, l, mid, L, mid), query(t[now].rs, mid + 1, r, mid + 1, R));
	return max(ret, t[now].lzy);
}

int dep[maxn], f[maxn];
In void dfs1(int now, int _f)
{
	f[now] = min(f[_f] + 1, dep[now] - query(root[_f], 1, n, dfsx[now], dfsx[now] + siz[now] - 1));
	insert(root[_f], root[now], 1, n, dfsx[now], dfsx[now] + siz[now] - 1, dep[now]);
	for(auto v : v1[now])
	{
		if(v == _f) continue;
		dep[v] = dep[now] + 1;
		dfs1(v, now);
	}
}

In void init()
{
	for(int i = 1; i <= n; ++i) v1[i].clear(), v2[i].clear();
	Mem(root, 0), cnt = tcnt = 0;
}

int main()
{
	int T = read();
	while(T--)
	{
		n = read();
		init();
		for(int i = 1, x, y; i < n; ++i) x = read(), y = read(), v1[x].pb(y), v1[y].pb(x);
		for(int i = 1, x, y; i < n; ++i) x = read(), y = read(), v2[x].pb(y), v2[y].pb(x);
		dfs2(1, 0);
		dep[1] = 1, dfs1(1, 0);
		int Max = 0;
		for(int i = 1; i <= n; ++i) Max = max(Max, f[i]);
		write(Max), enter;
	}
	return 0;
}
posted @ 2021-08-09 20:35  mrclr  阅读(76)  评论(0编辑  收藏  举报