[LOJ#2330]「清华集训 2017」榕树之心
[LOJ#2330]「清华集训 2017」榕树之心
试题描述
深秋。冷风吹散了最后一丝夏日的暑气,也吹落了榕树脚下灌木丛的叶子。相识数年的Evan和Lyra再次回到了小时候见面的茂盛榕树之下。小溪依旧,石桥依旧,榕树虽是历经荣枯更迭,依旧亭亭如盖,只是Evan和Lyra再也不是七八年前不经世事的少年了。
……
“已经快是严冬了,榕树的叶子还没落呢……”
“榕树是常绿树,是看不到明显的落叶季节的……”
“唉……想不到已经七年了呢。榕树还是当年的榕树,你却不是当年的你了……”
“其实又有什么是一成不变的呢,榕树常绿,翠绿树冠的宏观永恒,是由无数细小树叶的荣枯更迭组成的。在时间的流逝中一切都在不断变化着呢……”
“但你看这榕树,日日如此,季季如此,年年如此,仿佛亘古不变般,盘根错节,郁郁葱葱。我在想,或许成为一棵树更好吧,任时间从枝叶间流过,我只守这一片绿荫就好。”
“榕树固然长久,但在这无限的时光里,终归是要湮灭于尘土的。与其像榕树一般,植根于一方泥土中感受年复一年的四季更替。倒不如在有限的时间里看过尽可能多的世界吧。再说了,榕树虽生长缓慢,却依旧会在每年春天抽出一根新的枝条去向外探索的呢……”
“真的吗,榕树在她漫长的一生里,就是这样往外一步步探索的吗?”
“毕竟就算树冠看起来一成不变,榕树也会随着时间周期变化,春天到了自然就是生长的时候了,她也应当做出对应的表现吧……”
“相比于对季节更替做出本能的生长,我倒宁愿相信,榕树有一颗活跃的的,探索的心。”
“其实榕树是有心的,榕树刚刚种下的时候,心就在根的地方发芽了。以后每年春天榕树长出新枝条的时候,心就会向着新枝条的方向移动一点,这样就能更靠近外面的世界了。你看这头顶上的枝条,纵横交错,其实心已经在这枝杈间,移动了数十载了呢……”
“哇,也就是说,这密密麻麻的树杈中的某个地方,藏着这棵榕树的心吗?”
“没错,可是要知道它在哪,就得另花一番功夫了……”
“呀,这时候想想,一株树还是不如一个人好……比如你,要是这样贴上去的话,就能听到跳动的声音呢……”
……
一棵榕树可以抽象成一棵 \(n\) 个结点的有根树,其中结点编号为 \(1 \sim n\),而 \(1\) 号点就是根节点。初始时,树只有一号点,而心也在一号点。之后每一步,树都会长出一个新结点,即某个和当前已经存在的某个结点相邻的结点被加入了树中,之后,心会沿着心到新加结点的简单路径移动一步。这棵 \(n\) 个结点的树有很多种生长的顺序,不同的顺序可能会导致最终心的位置不同。现在,Evan和Lyra想知道,哪些结点可能是心在生长过程结束时停留的位置呢?
例如一棵大小为 \(4\) 的树,连边为 \(\{<1,2>,<1,3>,<1,4>\}\),我们有三种不同的生长顺序可以让心分别停留在 \(2,3,4\) 号节点上:
最终停留在 \(2\) 号点:
- 从 \(1\) 生长出 \(3\),心从 \(1\) 移动到 \(3\),
- 从 \(1\) 生长出 \(4\),心从 \(3\) 移动回 \(1\),
- 从 \(1\) 生长出 \(2\),心从 \(1\) 移动到 \(2\).
最终停留在 \(3\) 号点:
- 从 \(1\) 生长出 \(2\),心从 \(1\) 移动到 \(2\),
- 从 \(1\) 生长出 \(4\),心从 \(2\) 移动回 \(1\),
- 从 \(1\) 生长出 \(3\),心从 \(1\) 移动到 \(3\).
最终停留在 \(4\) 号点:
- 从 \(1\) 生长出 \(2\),心从 \(1\) 移动到 \(2\),
- 从 \(1\) 生长出 \(3\),心从 \(2\) 移动回 \(1\),
- 从 \(1\) 生长出 \(4\),心从 \(1\) 移动到 \(4\).
而我们可以证明,不存在任何一种可能的生长顺序使得心停留在 \(1\) 号点。
输入
从标准输入读入数据。
输入第一行一个两个正整数 \(W, T\),分别表示子任务编号(在样例中 \(W=0\))和数据组数,接下来是 \(T\) 组数据的描述,对于每组数据:
第一行一个正整数 \(n\) 表示树上结点的个数。
接下来 \(n-1\) 行,每行两个正整数 \(ia_i,b_i\),表示编号 \(a_i,b_i\) 的结点间有一条树边,保证 \(a_i \neq b_i\) 并且输入的 \(n-1\) 条边恰好构成了一棵树。
输出
输出到标准输出。
若输入的 \(W\) 不等于 \(3\),对于每组数据输出一行一个长度为 \(n\) 的 \(01\) 字符串,表示编号为 \(1 \sim n\) 的结点是否有可能是心最后所在的位置,若 \(01\) 字符串对应位是 \(1\) 则表示可能,为 \(0\) 则表示不可能。
若输入的 \(W\) 等于 \(3\),则对每组数据输出一个字符表示 \(1\) 号点的答案。
输入示例
0 3
4
1 2
1 3
1 4
6
1 2
1 3
1 4
4 5
5 6
10
1 2
1 3
3 4
3 5
3 6
4 7
7 8
8 9
9 10
输出示例
0111
000101
0000001010
数据规模及约定
Subtask 1[10pts]
\(T \leq 50; n \leq 15\)。
Subtask 2[10pts]
\(T \leq 20; n \leq 10^5\)。 除了 \(1\) 号点之外,每个点度数(包括父亲)不超过 \(2\)。
Subtask 3[10pts]
\(T \leq 200; n \leq 100\)。 只输出一个字符表示 \(1\) 号点答案,即保证 \(1\) 号点答案正确即可。
Subtask 4[35pts]
\(T \leq 20; n \leq 10^3\)。
Subtask 5[35pts]
\(T \leq 20; n \leq 10^5\)。
题解
考虑“生长”效果可以互相消去。先考虑只用判断能否到达根节点的子任务,我们发现当且仅当所有生长效果可以被消干净时才有可能最后停到根节点。
然后对于所有子树,最难消的肯定是最大的那颗子树,并且可以直观感觉到,最大的子树越小,越容易消干净,那么怎么量化这个感觉呢?
我们不妨设 \(elim(i)\) 表示对于子树 \(i\),它最多能消掉的对数,令 \(siz_i\) 表示子树 \(i\) 中的节点数,\(s_m\) 为 \(i\) 最大的儿子。
那么当 \(siz_{s_m} \le siz_i - 1 - siz_{s_m}\) 时,可以用其他点把子树 \(s_m\) 全部消掉,如果其他点多出来了,它们之间可以互相消掉,唯一可能剩下的情况就是 \(siz_i - 1\) 为奇数,那么肯定会剩下一个节点。
当 \(siz_{s_m} > siz_i - 1 - siz_{s_m}\) 时,就考虑先让儿子 \(s_m\) 自己消,消到一定时候我们用兄弟助他一臂之力,看能不能消光;即 \(2 \cdot elim(s_m) + siz_i - 1 - siz_{s_m} \ge siz_{s_m}\) 时,能够全消光(奇数的话剩一个),否则能消的对数就是 \(elim(s_m) + siz_i - 1 - siz_{s_m}\)。
那么再考虑满分做法,对于节点 \(x\),如果最终能够到达,相当于把 \(1 \sim x\) 的路径缩成新的根,然后其他的子树互相消就好了。这样我们就可以 dfs 的同时维护需要的信息(\(elim(i)\),\(\mathrm{max}\{siz_i\}\) 等,这些信息可以很容易地合并,所以能够维护),然后遍历到的点就可以判断是否能够到达了。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxn 100010
#define maxm 200010
int n, m, head[maxn], nxt[maxm], to[maxm];
void AddEdge(int a, int b) {
to[++m] = b; nxt[m] = head[a]; head[a] = m;
swap(a, b);
to[++m] = b; nxt[m] = head[a]; head[a] = m;
return ;
}
int siz[maxn], elim[maxn], ms[maxn], ms2[maxn];
void getElim(int u, int fa) {
siz[u] = 1;
ms[u] = ms2[u] = -1;
for(int e = head[u]; e; e = nxt[e]) if(to[e] != fa) {
getElim(to[e], u);
siz[u] += siz[to[e]];
if(ms[u] < 0) ms[u] = to[e];
else if(siz[to[e]] > siz[ms[u]]) ms2[u] = ms[u], ms[u] = to[e];
else if(ms2[u] < 0 || siz[to[e]] > siz[ms2[u]]) ms2[u] = to[e];
}
int others = 0;
for(int e = head[u]; e; e = nxt[e]) if(to[e] != fa && to[e] != ms[u])
others += siz[to[e]];
if(others >= siz[ms[u]] - (elim[ms[u]] << 1)) elim[u] = siz[u] - 1 >> 1;
else elim[u] = elim[ms[u]] + others;
return ;
}
bool can[maxn];
void getCan(int u, int fa, int nsiz, int nelim, int nothers) {
if(ms[u] < 0) {
// printf("leave %d: %d %d %d\n", u, nsiz, nelim, nothers);
if(nothers >= nsiz - (nelim << 1) && !(nsiz + nothers & 1)) can[u] = 1;
else can[u] = 0;
return ;
}
int siza, ela, others = nothers + siz[u] - siz[ms[u]] - 1;
if(siz[ms[u]] > nsiz) siza = siz[ms[u]], ela = elim[ms[u]], others += nsiz;
else siza = nsiz, ela = nelim, others += siz[ms[u]];
// printf("node %d: %d %d %d\n", u, siza, ela, others);
if(others >= siza - (ela << 1) && !(siza + others & 1)) can[u] = 1;
else can[u] = 0;
for(int e = head[u]; e; e = nxt[e]) if(to[e] != fa) {
int nxsiz, nxelim, nxothers = nothers;
if(to[e] == ms[u]) {
nxothers += siz[u] - siz[to[e]] - 1 - (ms2[u] < 0 ? 0 : siz[ms2[u]]);
if(ms2[u] < 0 || nsiz > siz[ms2[u]]) nxsiz = nsiz, nxelim = nelim, nxothers += siz[ms2[u]];
else nxsiz = siz[ms2[u]], nxelim = elim[ms2[u]], nxothers += nsiz;
getCan(to[e], u, nxsiz, nxelim, nxothers);
}
else {
nxothers += siz[u] - siz[to[e]] - 1 - siz[ms[u]];
if(nsiz > siz[ms[u]]) nxsiz = nsiz, nxelim = nelim, nxothers += siz[ms[u]];
else nxsiz = siz[ms[u]], nxelim = elim[ms[u]], nxothers += nsiz;
getCan(to[e], u, nxsiz, nxelim, nxothers);
}
}
return ;
}
int main() {
int W = read(), T = read();
while(T--) {
n = read();
m = 0; memset(head, 0, sizeof(head));
rep(i, 1, n - 1) {
int a = read(), b = read();
AddEdge(a, b);
}
memset(elim, 0, sizeof(elim));
getElim(1, 0);
// rep(i, 1, n) printf("%d%c", elim[i], i < n ? ' ' : '\n');
getCan(1, 0, 0, 0, 0);
if(W != 3){ rep(i, 1, n) putchar((int)can[i] + '0'); putchar('\n'); }
else{ putchar((int)can[1] + '0'); putchar('\n'); }
}
return 0;
}
[LOJ#2330]「清华集训 2017」榕树之心的更多相关文章
- 2018.09.01 loj#2330. 「清华集训 2017」榕树之心(树形dp)
传送门 树形dp好题啊. 我们用w[i]" role="presentation" style="position: relative;">w[ ...
- Loj #2331. 「清华集训 2017」某位歌姬的故事
Loj #2331. 「清华集训 2017」某位歌姬的故事 IA 是一名会唱歌的女孩子. IOI2018 就要来了,IA 决定给参赛选手们写一首歌,以表达美好的祝愿.这首歌一共有 \(n\) 个音符, ...
- Loj #2324. 「清华集训 2017」小 Y 和二叉树
Loj #2324. 「清华集训 2017」小 Y 和二叉树 小Y是一个心灵手巧的OIer,她有许多二叉树模型. 小Y的二叉树模型中,每个结点都具有一个编号,小Y把她最喜欢的一个二叉树模型挂在了墙上, ...
- Loj #2321. 「清华集训 2017」无限之环
Loj #2321. 「清华集训 2017」无限之环 曾经有一款流行的游戏,叫做 *Infinity Loop***,先来简单的介绍一下这个游戏: 游戏在一个 \(n \times m\) 的网格状棋 ...
- Loj 2320.「清华集训 2017」生成树计数
Loj 2320.「清华集训 2017」生成树计数 题目描述 在一个 \(s\) 个点的图中,存在 \(s-n\) 条边,使图中形成了 \(n\) 个连通块,第 \(i\) 个连通块中有 \(a_i\ ...
- [LOJ#2329]「清华集训 2017」我的生命已如风中残烛
[LOJ#2329]「清华集训 2017」我的生命已如风中残烛 试题描述 九条可怜是一个贪玩的女孩子. 这天她在一堵墙钉了 \(n\) 个钉子,第 \(i\) 个钉子的坐标是 \((x_i,y_i)\ ...
- [LOJ#2328]「清华集训 2017」避难所
[LOJ#2328]「清华集训 2017」避难所 试题描述 "B君啊,你当年的伙伴都不在北京了,为什么你还在北京呢?" "大概是因为出了一些事故吧,否则这道题就不叫避难所 ...
- [LOJ#2327]「清华集训 2017」福若格斯
[LOJ#2327]「清华集训 2017」福若格斯 试题描述 小d是4xx9小游戏高手. 有一天,小d发现了一个很经典的小游戏:跳青蛙. 游戏在一个 \(5\) 个格子的棋盘上进行.在游戏的一开始,最 ...
- [LOJ#2326]「清华集训 2017」简单数据结构
[LOJ#2326]「清华集训 2017」简单数据结构 试题描述 参加完IOI2018之后就是姚班面试.而你,由于讨厌物理.并且想成为乔布斯一样的创业家,被成功踢回贵系. 转眼,时间的指针被指向201 ...
随机推荐
- 管道命令'|' 和xargs find命令找到后把所有找到的删除
管道符号,是unix功能强大的一个地方,符号是一条竖线:"|", 用法: command 1 | command 2 他的功能是把第一个命令command 1执行的结果作为comm ...
- python_12_continue
for i in range(9): if i<3: print("loop",i) else: continue#跳出本次循环,继续到下一循环 print('hehe... ...
- 支持向量机: Maximum Margin Classifier
支持向量机即 Support Vector Machine,简称 SVM .我最开始听说这头机器的名号的时候,一种神秘感就油然而生,似乎把 Support 这么一个具体的动作和 Vector 这么一个 ...
- 国产中标麒麟Linux部署dotnet core 环境并运行项目 (一) 安装dotnet core
背景 根据我之前写的文章 将 Net 项目升级 Core项目经验:(一)迁移Net项目为Net Core\Standard项目,我们将公司内部最核心的ORM框架迁移到net core 上面,并在win ...
- 通过LDB_PROCESS函数使用逻辑数据库
1.概览 通过LDB_PROCESS函数可以允许任何程序访问逻辑数据库,允许一个程序访问多个逻辑数据库,当然也允许多次连续访问访问同个逻辑数据库.当使用LDB_PROCESS函数来访问逻辑数据库 ...
- k8s的ingress资源简述
ingress controller是独立与controller-manager的Ingress的主要作用是可以利用nginx,haproxy,envoy,traefik等负载均衡器来暴露集群内部服务 ...
- linux中搭建公网ftp服务器
Linux搭建ftp服务器汇总整理 一.检查linux中是否已经安装vsftpd服务端软件 rpm -qa |grep vsftpd 二.卸载linux中的vsftpd服务端软件 rpm -e vsf ...
- js 发送验证码倒计时
首先写一个按钮: <input type="button" id="btn" value="免费获取验证码" onclick=&quo ...
- JZOJ 5462. 【NOIP2017提高A组冲刺11.8】好文章
5462. [NOIP2017提高A组冲刺11.8]好文章 (File IO): input:article.in output:article.out Time Limits: 1000 ms M ...
- 01 python爬虫
---