【清华集训2017】榕树之心

简要题意:(考虑到某些人搜题解只是懒得看题面)
特殊点从 \(1\) 号点出发,每次选择一个与当前选择连通块相连的点,加入连通块,并且把我们的特殊点向那个选的点移动一格。对每个点求是否能成为我们的特殊点的终点。\(1\) 处在一开始的连通块之中。

不错的一道题。但是如果要很快地写对需要冷静思考(像我就不知道在干什么)。像我这样的菜鸡看了题解才会……

先考虑简单的部分,即 \(W = 3\),只输出 \(1\) 的答案。

先根据特殊点的深度的奇偶性来判断一个点有没有可能(即 \(dep_x + n\) 的奇偶性)。

根据套路,如果有一个子树的大小大于总大小的一半,那么那个子树显然能贡献很多,但是不一定会无解。如果不存在,显然能构造出一个方案(即分成三部分,一个部分里只有一个,另外两个部分大小分别都小于一半,显然有解)。

所以现在只要考虑那个最大的子树,如果我们把它消的小了,那么还是有可能的。记 \(f_i\) 为向 \(i\) 走能造成的最小的相对深度(不是相对 \(i\) 而是相对走到 \(i\) 的那个点,即 \(i\) 也会对这个深度产生贡献。这样做能方便各种地方的细节)。

显然,考虑如果 \(u\) 向 最大的子树 \(v\) 走,此时如果 \(sz_u - sz_v - 1\) 即其他子树的大小是比 \(f_v\) 大的,又因为 \(sz_v > sz_u - sz_v - 1\),那么显然能构造出一种方案,使得 \(v\) 的贡献与 \(sz_u - sz_v - 1\) 相差不超过 \(1\) 。所以直接根据奇偶性进行转移,即 \(f_u = sz_u - 1 \mod 2\)

但是如果这个比不上 \(f_v\) ,那么其他的子树肯定是要拿去消 \(f_v\) 的,所以 \(f_u = f_v - (sz_u - sz_v - 1) + 1\)

所以现在已经做完这个简单的部分了。考虑其他点的情况。因为路径上的点是必经的,所以如果把这条路径缩成一个点,上述的过程依然能进行。(因为如果点可行,那就是对消的,那么在链上显然可以)

所以我们仍维护那个最大的子树。因为一条链都缩成一个点了,那么这些子树都是原树某个点的子树。因此不必要考虑换根DP的各种细节,直接类似上面的过程处理下去即可。

注意数据的清空。同时因为变量初始化的原因,以及转移没推清楚,先RE了一发,又WA成45了一发。

#include <bits/stdc++.h>

const int MAXN = 100010;

int head[MAXN], nxt[MAXN << 1], to[MAXN << 1], tot;
void addedge(int b, int e) {
    nxt[++tot] = head[b]; to[head[b] = tot] = e;
    nxt[++tot] = head[e]; to[head[e] = tot] = b;
}
int sz[MAXN], fir[MAXN], sec[MAXN];
int f[MAXN], ansl[MAXN], n;
int cmp(int a, int b) { // a >= b
    return sz[a] == sz[b] ? f[a] <= f[b] : sz[a] > sz[b];
}
void gx(int & fir, int & sec, int v) {
    if (cmp(v, fir)) sec = fir, fir = v;
    else if (cmp(v, sec)) sec = v;
}
void dfs1(int u, int fa = 0) {
    sz[u] = 1;
    for (int i = head[u]; i; i = nxt[i])
        if (to[i] != fa) {
            dfs1(to[i], u);
            sz[u] += sz[to[i]];
            gx(fir[u], sec[u], to[i]);
        }
    if (!fir[u]) return ;
    int R = sz[u] - 1 - sz[fir[u]];
    if (R > f[fir[u]]) f[u] = sz[u] + 1 & 1; else f[u] = f[fir[u]] + 1 - R;
    // std::cout << u << ' ' << sz[u] << " : " << fir[u] << ' ' << sec[u] << " : " << f[u] << ' ' << R << std::endl;
}
void dfs2(int u, int fa = 0, int ma = 0, int dep = 0) {
    int t, v;
    gx(v = ma, t = 0, fir[u]);
    if (dep + n & 1)
        ansl[u] = n - dep - 1 - sz[v] >= f[v];
    for (int i = head[u]; i; i = nxt[i]) if (to[i] != fa) {
        gx(v = ma, t = 0, to[i] == fir[u] ? sec[u] : fir[u]);
        dfs2(to[i], u, v, dep + 1);
    }
}
int main() {
    // freopen("1.in", "r", stdin);
    std::ios_base::sync_with_stdio(false), std::cin.tie(0);
    int W, T; std::cin >> W >> T;
    while (T --> 0) {
        std::cin >> n;
        for (int i = 1, t1, t2; i < n; ++i) {
            std::cin >> t1 >> t2;
            addedge(t1, t2);
        }
        dfs1(1); dfs2(1);
        for (int i = 1; i <= (W == 3 ? 1 : n); ++i)
            std::cout << ansl[i];
        std::cout << '\n';
        memset(head, 0, n + 1 << 2); tot = 0;
        memset(ansl, 0, n + 1 << 2);
        memset(fir, 0, n + 1 << 2);
        memset(sec, 0, n + 1 << 2);
        memset(f, 0, n + 1 << 2);
    }
    return 0;
}

转载于:https://www.cnblogs.com/daklqw/p/11574871.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值