1. 程式人生 > 其它 >2021牛客暑期多校訓練營7 F-xay loves trees

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;
}