【CSP-S 2019】【洛谷P5666】树的重心【主席树】【树状数组】【dfs】
题目:
题目链接:https://www.luogu.com.cn/problem/P5666
小简单正在学习离散数学,今天的内容是图论基础,在课上他做了如下两条笔记:
- 一个大小为 \(n\) 的树由 \(n\) 个结点与 \(n − 1\) 条无向边构成,且满足任意两个结点间有且仅有一条简单路径。在树中删去一个结点及与它关联的边,树将分裂为若干个子树;而在树中删去一条边(保留关联结点,下同),树将分裂为恰好两个子树。
- 对于一个大小为 \(n\) 的树与任意一个树中结点 \(c\),称 \(c\) 是该树的重心当且仅当在树中删去 \(c\) 及与它关联的边后,分裂出的所有子树的大小均不超过 \(\lfloor \frac{n}{2} \rfloor\)(其中 \(\lfloor x \rfloor\) 是下取整函数)。对于包含至少一个结点的树,它的重心只可能有 1 或 2 个。
课后老师给出了一个大小为 \(n\) 的树 \(S\),树中结点从 \(1 \sim n\) 编号。小简单的课后作业是求出 \(S\) 单独删去每条边后,分裂出的两个子树的重心编号和之和。即:
\]
上式中,\(E\) 表示树 \(S\) 的边集,\((u,v)\) 表示一条连接 \(u\) 号点和 \(v\) 号点的边。\(S'_u\) 与 \(S'_v\) 分别表示树 \(S\) 删去边 \((u,v)\) 后,\(u\) 号点与 \(v\) 号点所在的被分裂出的子树。
小简单觉得作业并不简单,只好向你求助,请你教教他。
思路:
总算\(A\)了\(qwq\),我太菜了。
考虑每一个点能有哪些边可以对他做贡献。
假设现在树根为\(1\),分两种情况:
如果我们不在\(1\)号节点的重儿子中割边,那么我们只要保证割边之后这棵树的大小不小于\(2\times\)重儿子大小即可。
我们设割去边的另一棵树的大小为\(t\),那么也就是说我们只要保证\(2\times max1\leq n-t\),也就是\(t\leq n-2\times max1\)。如果我们在\(1\)号节点的重儿子重割边,那么\(1\)号节点的重儿子可能改变也可能不改变。
如果改变,设\(max2\)表示\(1\)号节点原来的第二大子树的大小,那么我们需要满足\(2\times max2\leq n-t\),也就是\(t\leq n-2\times max2\)。
如果不改变,那么我们需要满足\(2\times (max1-t)\leq n-t\),也就是\(t\geq 2\times max1-n\)
综上,在\(1\)号节点的重儿子重割边只要满足\(2\times max1-n\leq t\leq n-2\times max2\)即可。
那么我们如果可以求出\(1\)号节点每一颗子树的大小,以及在每一棵子树内的有多少个大小为\(t\)的子树,并且支持区间查询(这样就可以求出一颗子树内有多少个子树大小取值在任意区间\([l,r]\)了),那么就可以完成这道题。
我们可以用主席树来维护以\(1\)为根时,\(dfs\)序在\([x,y]\)之间的所有节点,有多少个大小在\([l,r]\)之内。这样就可以直接完成\(1\)为根的计算。
考虑换根,我们把根从\(1\to x\)时,我们发现,以\(x\)为根的子树分为两种:以\(1\)为根时,在\(x\)的子树下的所有子树 和 换根后\(1\)与\(1\)的其他子树所构成的一颗子树。此时求前者的\(t\)是没有问题的,但是要求后者的\(t\),我们考虑用整棵树的\(t\)的取值个数\(-\)前者的\(t\)的取值个数即可。
整棵树的\(t\)的取值个数可以用树状数组动态维护。
累计答案时分类讨论一下当前节点的重儿子是前者还是后者即可。
时间复杂度\(O(n\log n)\)
代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=300010;
int T,n,tot,head[N],size[N],id[N],rk[N],root[N];
ll ans;
struct edge
{
int next,to,dis;
}e[N*2];
struct Treenode
{
int lc,rc,cnt;
};
struct BIT
{
int c[N];
void clr()
{
memset(c,0,sizeof(c));
}
void add(int x,int val)
{
if (x<=0) return;
for (int i=x;i<=n;i+=i&-i)
c[i]+=val;
}
int ask(int x)
{
if (x<=0) return 0;
int sum=0;
for (int i=x;i;i-=i&-i)
sum+=c[i];
return sum;
}
}bit;
struct Tree
{
Treenode tree[N*50];
int tot;
void clr()
{
memset(tree,0,sizeof(tree));
tot=0;
}
int build(int l,int r)
{
int p=++tot;
if (l==r) return p;
int mid=(l+r)>>1;
tree[p].lc=build(l,mid);
tree[p].rc=build(mid+1,r);
return p;
}
int update(int now,int l,int r,int k)
{
int p=++tot;
tree[p]=tree[now]; tree[p].cnt++;
if (l==r) return p;
int mid=(l+r)>>1;
if (k<=mid) tree[p].lc=update(tree[now].lc,l,mid,k);
else tree[p].rc=update(tree[now].rc,mid+1,r,k);
return p;
}
int ask(int nowl,int nowr,int l,int r,int ql,int qr)
{
if (ql==l && qr==r)
return tree[nowr].cnt-tree[nowl].cnt;
if (ql>qr) return 0;
int mid=(l+r)>>1;
if (qr<=mid) return ask(tree[nowl].lc,tree[nowr].lc,l,mid,ql,qr);
else if (ql>mid) return ask(tree[nowl].rc,tree[nowr].rc,mid+1,r,ql,qr);
else return ask(tree[nowl].lc,tree[nowr].lc,l,mid,ql,mid)+ask(tree[nowl].rc,tree[nowr].rc,mid+1,r,mid+1,qr);
}
}Tree;
void add(int from,int to)
{
e[++tot].to=to;
e[tot].next=head[from];
head[from]=tot;
}
int dfs1(int x,int fa)
{
size[x]=1; id[x]=++tot; rk[tot]=x;
for (int i=head[x];~i;i=e[i].next)
if (e[i].to!=fa) size[x]+=dfs1(e[i].to,x);
bit.add(size[x],1);
// root[id[x]]=Tree.update(root[id[x]-1],1,n,size[x]);
return size[x];
}
void add_ans(int x,int fa)
{
int max1=0,max2=0,pos;
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (size[v]>max1) max2=max1,max1=size[v],pos=v;
else if (size[v]>max2) max2=size[v];
}
if (pos!=fa)
{
int cnt_in=Tree.ask(root[id[pos]-1],root[id[pos]+size[pos]-1],1,n,1,n-max1*2);
int cnt_all=bit.ask(n-max1*2);
int cnt=Tree.ask(root[id[pos]-1],root[id[pos]+size[pos]-1],1,n,max(max1*2-n,1),n-max2*2);
ans+=1LL*x*(cnt_all-cnt_in+cnt);
}
else
{
int cnt=Tree.ask(root[id[x]],root[id[x]+size[x]-1],1,n,1,n-max1*2);
int cnt_all=bit.ask(n-max2*2)-bit.ask(max(max1*2-n-1,0));
int cnt_in=Tree.ask(root[id[x]],root[id[x]+size[x]-1],1,n,max(max1*2-n,1),n-max2*2);
ans+=1LL*x*(cnt_all-cnt_in+cnt);
}
}
void dfs2(int x,int fa)
{
bit.add(size[x],-1);
add_ans(x,fa);
int Cpy=size[x];
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (v!=fa)
{
size[x]=n-size[v];
bit.add(size[x],1);
dfs2(e[i].to,x);
bit.add(size[x],-1);
}
}
size[x]=Cpy;
bit.add(size[x],1);
}
int main()
{
scanf("%d",&T);
while (T--)
{
memset(head,-1,sizeof(head));
tot=ans=0;
scanf("%d",&n);
Tree.clr(); bit.clr();
root[0]=Tree.build(1,n);
for (int i=1,x,y;i<n;i++)
{
scanf("%d%d",&x,&y);
add(x,y); add(y,x);
}
tot=0;
dfs1(1,0);
for (int i=1;i<=n;i++)
root[i]=Tree.update(root[i-1],1,n,size[rk[i]]);
dfs2(1,0);
printf("%lld\n",ans);
}
return 0;
}
【CSP-S 2019】【洛谷P5666】树的重心【主席树】【树状数组】【dfs】的更多相关文章
- 洛谷 P3377 【模板】左偏树(可并堆)
洛谷 P3377 [模板]左偏树(可并堆) 题目描述 如题,一开始有N个小根堆,每个堆包含且仅包含一个数.接下来需要支持两种操作: 操作1: 1 x y 将第x个数和第y个数所在的小根堆合并(若第x或 ...
- 洛谷P3377 【模板】左偏树(可并堆) 题解
作者:zifeiy 标签:左偏树 这篇随笔需要你在之前掌握 堆 和 二叉树 的相关知识点. 堆支持在 \(O(\log n)\) 的时间内进行插入元素.查询最值和删除最值的操作.在这里,如果最值是最小 ...
- 【BZOJ】2434: [Noi2011]阿狸的打字机 AC自动机+树状数组+DFS序
[题意]阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母. 经阿狸研究发现,这个打字机是这样工作的: l 输入小写 ...
- 括号树 noip(csp??) 2019 洛谷 P5658
洛谷AC通道 本题,题目长,但是实际想起来十分简单. 首先,对于树上的每一个后括号,我们很容易知道,他的贡献值等于上一个后括号的贡献值 + 1.(当然,前提是要有人跟他匹配,毕竟题目中要求了,是不同的 ...
- 格雷码 CSP(NOIP??)2019 洛谷 P5657
洛谷AC通道! 多年过后,重新来看这道D1T1,20min不到AC,再回忆起当初考场三小时的抓耳挠腮,不禁感慨万千啊!! 发篇题解记录一下. 思路:直接dfs模拟即可(二进制找规律是不可能的, 这辈子 ...
- 洛谷P3434 [POI2006]KRA-The Disks(线段树)
洛谷题目传送门 \(O(n)\)的正解算法对我这个小蒟蒻真的还有点思维难度.洛谷题解里都讲得很好. 考试的时候一看到300000就直接去想各种带log的做法了,反正不怕T...... 我永远只会有最直 ...
- 洛谷P2617 Dynamic Ranking(主席树,树套树,树状数组)
洛谷题目传送门 YCB巨佬对此题有详细的讲解.%YCB%请点这里 思路分析 不能套用静态主席树的方法了.因为的\(N\)个线段树相互纠缠,一旦改了一个点,整个主席树统统都要改一遍...... 话说我真 ...
- 洛谷.3733.[HAOI2017]八纵八横(线性基 线段树分治 bitset)
LOJ 洛谷 最基本的思路同BZOJ2115 Xor,将图中所有环的异或和插入线性基,求一下线性基中数的异或最大值. 用bitset优化一下,暴力的复杂度是\(O(\frac{qmL^2}{w})\) ...
- 洛谷AT2046 Namori(思维,基环树,树形DP)
洛谷题目传送门 神仙思维题还是要写点东西才好. 树 每次操作把相邻且同色的点反色,直接这样思考会发现状态有很强的后效性,没办法考虑转移. 因为树是二分图,所以我们转化模型:在树的奇数层的所有点上都有一 ...
随机推荐
- 手把手带你写一个minishell
先解释一下Shell : Shell是一个功能为命令行解释器的应用程序,连接了用户和Linux内核,让我们能高效和安全地使用Linux内核. 要写一个minishell,我们要先理解它的过程: 读取输 ...
- Go 参数传递
Go参数传递 在面试中,经常会被问起,这门语言的参数传递是值传递还是引用传递,当然,大部分情况下我们都会提前准备,有恃无恐,但还是希望能够精益求精嘛,所以针对Go语言来分析,Go传参是值传递还是引用传 ...
- es6新特性-解构表达式、Lambda表达式、局部变量及map/reduce方法
循环内的变量在循环外可见,不合理: let定义的变量是局部变量: const修饰的是常量,不允许再次修改,类似于java中的static: 解构表达式:
- 【转载】终于有人把“TCC分布式事务”的实现原理讲明白了
之前网上看到很多写分布式事务的文章,不过大多都是将分布式事务各种技术方案简单介绍一下.很多朋友看了还是不知道分布式事务到底怎么回事,在项目里到底如何使用. 所以这篇文章,就用大白话+手工绘图,并结合一 ...
- WPF 不要给 Window 类设置变换矩阵(应用篇)
原文:WPF 不要给 Window 类设置变换矩阵(应用篇) WPF 的 Window 类是不允许设置变换矩阵的.不过,总会有小伙伴为了能够设置一下试图绕过一些验证机制. 不要试图绕过,因为你会遇到更 ...
- AspNetCore网关集成Swagger访问使用IdentityServer保护的webapi项目
创建webapi项目 创建四个webapi项目,两个处理业务,一个网关,一个验证中心.四个项目对应的端口如下, ApiGateway:1999 IdentityServer:16690 Service ...
- 关闭 OSX 10.11 SIP (System Integrity Protection) 功能
关闭 OSX 10.11 SIP (System Integrity Protection) 功能 来源 https://cms.35g.tw/coding/%E9%97%9C%E9%96%89-os ...
- fatal: 不是一个有效的对象名:'master'。
听说git比svn的branch功能好,所以装了个msysgit玩.执行完了git init操作后想建branch,用git branch develop命令,结果报错,说 fatal: Not a ...
- C++线程同步与互斥总结
互斥:当多个线程访问同一个全局变量,或者同一个资源(比如打印机)的时候,需要进行线程间的互斥操作来保证访问的安全性. 临界区.互斥体.事件和信号量都可以实现线程互斥.但如果仅仅需要实现互斥功能,推荐前 ...
- Delphi-RzDbgrid-绘制表格式设置某行颜色或者其他格式-以及隔行换色的属性
参考文章:https://www.cnblogs.com/OSKnown/p/8568740.html 在DbgridEh和原生的Dbgrid直接在DrawColumnCell事件中写重绘代码就好了, ...