P4228 [清华集训2017] 榕树之心

给你一棵有根树和一个动点,动点一开始位于根结点 11

一开始这棵树只有根结点,每次可以在已有的结点上沿原树边向外扩展一个结点,并使动点沿着新结点的方向移动一个结点。

请求出每个结点在原树被完全扩展后,动点是否能停留在当前结点。

多测,T20,n105T \leq 20,n\leq 10^5,时限 3s3\text{s},空限 500MB500\text{MB}

sol

先考虑根节点的答案。

我们可以发现,假设动点当前在点 uu,那么我们将 uu 的两个不同子树分别做一次扩展,动点不动,称之为抵消。

根据套路,如果有一个根节点的子树的大小大于总大小(除去根节点)的一半,那么当这个子树被完全扩展后,根节点的其他子树无论如何也不能将它拉回根节点。

否则,即根节点的最大子树小于等于总大小(除去根节点)的一半,那么肯定有一种抵消方案使得最后除根节点外剩下 (n10)mod2(n-10)\bmod 2 个节点。

现在考虑如何让根节点的最大子树变小,越小越好。

我们发现,子树内是可以自行抵消的,即将动点拉倒子树内的一个点 vv,将 vv 的两个不同子树分别做一次扩展,动点仍然不动。

fuf_u 表示 uu 的子树内自行抵消后剩下的最小的节点数。

分类讨论,设 uu 的最大子树的根为 vv

  • fvsizu1sizvf_v\leq siz_u-1-siz_v,则 vv 的大小小于等于总大小(除去 uu,只考虑 uu 这个子树)的一半,则 fu=(sizu1)mod2f_u=(siz_u-1)\bmod 2
  • 否则,让其他子树全部去消 vv,最后剩下的是 vv 的一部分,则 fu=fv(sizu1sizv)f_u=f_v-(siz_u-1-siz_v)

注意,上面情况都没讨论 uu

为了让动点可以到达 uuuu 需要由 uu 的父亲向 uu 扩展一遍,所以这个贡献需要加上,最后令 fu++f_u++

对于根节点的答案,设其最大子树为 vv,需满足两个条件:

  • fvn1sizvf_v\leq n-1-siz_v,即其最大子树自行消除后剩余的节点根节点的其他子树可被消完。
  • n10(mod2)n-1\equiv 0\pmod 2,即其它子树互相消除后没有剩余节点。

再考虑其他节点(设其为 uu)的答案。

考虑一开始先将动点移动到 uu,则我们可以将 1u1 \to u 的这条链缩成一个点。

如下图:

由上图应该可以看到,如果我们将 1u1 \to u 的这条链缩成一个点的话,还需保存每个点的次大子树,然后像做根节点的时候一样做即可。

总时间复杂度为 O(Tn)\mathcal O(Tn),可过。

187ms / 16.99MB / 2.83KB C++98 O2\text{187ms / 16.99MB / 2.83KB C++98 O2}

#include <bits/stdc++.h>

#define re register

using namespace std;

namespace Fread
{
    const int SIZE = 1 << 23;
    char buf[SIZE], *S, *T;
    inline char getchar()
    {
        if (S == T)
        {
            T = (S = buf) + fread(buf, 1, SIZE, stdin);
            if (S == T)
                return '\n';
        }
        return *S++;
    }
}
namespace Fwrite
{
    const int SIZE = 1 << 23;
    char buf[SIZE], *S = buf, *T = buf + SIZE;
    inline void flush()
    {
        fwrite(buf, 1, S - buf, stdout);
        S = buf;
    }
    inline void putchar(char c)
    {
        *S++ = c;
        if (S == T)
            flush();
    }
    struct NTR
    {
        ~NTR()
        {
            flush();
        }
    } ztr;
}

#ifdef ONLINE_JUDGE
#define getchar Fread::getchar
#define putchar Fwrite::putchar
#endif

inline int read()
{
	re int x = 0, f = 1;
	re char c = getchar();
	while (c < '0' || c > '9')
	{
		if (c == '-')
			f = -1;
		c = getchar();
	}
	while (c >= '0' && c <= '9')
		x = x * 10 + c - '0', c = getchar();
	return x * f;
}

inline void write(int x)
{
	if(x < 0)
	{
		putchar('-');
		x = -x;
	}
	if(x > 9)
		write(x / 10);
	putchar(x % 10 + '0');
}

const int _ = 100007;

int n;

bool ans[_];

int cnt, head[_], to[_ << 1], nxt[_ << 1];

inline void add(int u, int v)
{
	to[++cnt] = v;
	nxt[cnt] = head[u];
	head[u] = cnt;
}

int dep[_], siz[_], hson[_], hson2[_], f[_];

inline int cmp(int x, int y)
{
	return siz[x] > siz[y] ? x : y;
}

inline void dfs1(int u, int fa)
{
	dep[u] = dep[fa] + 1, siz[u] = 1;
	for (re int i = head[u]; i; i = nxt[i])
	{
		int v = to[i];
		if (v == fa)
			continue;
		dfs1(v, u), siz[u] += siz[v];
		if (siz[v] > siz[hson[u]])
			hson2[u] = hson[u], hson[u] = v;
		else if (siz[v] > siz[hson2[u]])
			hson2[u] = v;
	}
	if (f[hson[u]] <= siz[u] - 1 - siz[hson[u]])
		f[u] = (siz[u] - 1) % 2;
	else
		f[u] = f[hson[u]] - (siz[u] - 1 - siz[hson[u]]);
	f[u]++;
}

inline void dfs2(int u, int fa, int h)
{
	int tmp = cmp(hson[u], h);
	if (f[tmp] <= n - dep[u] - siz[tmp])
		ans[u] = (n & 1) == (dep[u] & 1);
	for (re int i = head[u]; i; i = nxt[i])
	{
		re int v = to[i];
		if (v == fa)
			continue;
		dfs2(v, u, v == hson[u] ? cmp(hson2[u], h) : cmp(hson[u], h));
	}
}

inline void init()
{
	cnt = 0;
	memset(head, 0, sizeof head);
	memset(hson, 0, sizeof hson);
	memset(hson2, 0, sizeof hson2);
	memset(ans, 0, sizeof ans);
}

signed main()
{
	re int W = read(), T = read();
	while (T--)
	{
		init();
		n = read();
		for (re int i = 1, u, v; i < n; ++i)
		{
			u = read(), v = read();
			add(u, v), add(v, u);
		}
		dfs1(1, 0);
		dfs2(1, 0, 0);
		if (W == 3)
			write(ans[1]);
		else
			for (re int i = 1; i <= n; ++i)
				write(ans[i]);
		putchar('\n');
	}
	return 0;
}
posted @ 2022-02-10 09:14  蒟蒻orz  阅读(7)  评论(0编辑  收藏  举报  来源