题目大意

  给你一棵有根树,有\(n\)个点。还有一个参数\(k\)。你每次要删除一条长度为\(k\)(\(k\)个点)的祖先-后代链,问你最少几次删完。现在有\(q\)个询问,每次给你一个\(k\),问你答案是多少。

  \(n\leq {10}^5,k\leq {10}^9\)

题解

  设\(l\)为这棵树的叶子个数,显然当\(k>\)树的深度时答案都是\(l\)。

  下面要证明:答案是\(O(l+\frac{n-l}{k})\)的。

  我们从下往上贪心,每次选择一个未被覆盖的深度最深的点,覆盖这个点网上的一条链。我们把这些选择的点称为关键点。把所有关键点到父亲的连边断开。

  包含根的那个连通块和叶子节点的贡献是\(O(l)\)的。

  对于其他连通块,显然深度最深的点到关键点的距离是\(k-1\),所以连通块的大小不小于\(k\)。又因为每个连通块只有一个关键点,所以这部分的贡献是\(O(\frac{n-l}{k})\)的。

  对于每个\(k\),从叶子节点的上一层关键点开始暴力做就行了。查询一个点是否被覆盖就用DFS序+线段树(查询这个点的子树深度最浅的点深度是多少)。

  时间复杂度:\(O(n\log^2 n)\)(调和级数求和再加上数据结构的\(\log\))

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<ctime>
#include<utility>
#include<cmath>
#include<functional>
#include<queue>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
void sort(int &a,int &b)
{
if(a>b)
swap(a,b);
}
void open(const char *s)
{
#ifndef ONLINE_JUDGE
char str[100];
sprintf(str,"%s.in",s);
freopen(str,"r",stdin);
sprintf(str,"%s.out",s);
freopen(str,"w",stdout);
#endif
}
int rd()
{
int s=0,c;
while((c=getchar())<'0'||c>'9');
do
{
s=s*10+c-'0';
}
while((c=getchar())>='0'&&c<='9');
return s;
}
void put(int x)
{
if(!x)
{
putchar('0');
return;
}
static int c[20];
int t=0;
while(x)
{
c[++t]=x%10;
x/=10;
}
while(t)
putchar(c[t--]+'0');
}
int upmin(int &a,int b)
{
if(b<a)
{
a=b;
return 1;
}
return 0;
}
int upmax(int &a,int b)
{
if(b>a)
{
a=b;
return 1;
}
return 0;
}
vector<int> g[100010];
int f[100010][20];
int st[100010];
int ed[100010];
int d[100010];
int ti;
int maxd;
int leaves;
int s[100010];
vector<int> c[100010];
void dfs(int x,int fa,int dep)
{
maxd=max(maxd,dep);
f[x][0]=fa;
d[x]=dep;
st[x]=++ti;
int i;
for(i=1;i<=17;i++)
f[x][i]=f[f[x][i-1]][i-1];
int num=0;
s[x]=0x7fffffff;
for(auto v:g[x])
if(v!=fa)
{
dfs(v,x,dep+1);
s[x]=min(s[x],s[v]+1);
num++;
}
if(!num)
{
s[x]=0;
leaves++;
}
ed[x]=ti;
}
int jump(int x,int d)
{
int i;
for(i=17;i>=0;i--)
if(d&(1<<i))
x=f[x][i];
return x;
}
namespace seg
{
int ls[200010];
int rs[200010];
int s[200010];
int rt,n;
void add(int &p,int x,int v,int l,int r)
{
if(!p)
{
p=++n;
ls[p]=rs[p]=0;
s[p]=0x7fffffff;
}
if(l==r)
{
s[p]=v;
return;
}
int mid=(l+r)>>1;
if(x<=mid)
add(ls[p],x,v,l,mid);
else
add(rs[p],x,v,mid+1,r);
s[p]=0x7fffffff;
if(ls[p])
upmin(s[p],s[ls[p]]);
if(rs[p])
upmin(s[p],s[rs[p]]);
}
int query(int p,int L,int R,int l,int r)
{
if(!p)
return 0x7fffffff;
if(L<=l&&R>=r)
return s[p];
int res=0x7fffffff;
int mid=(l+r)>>1;
if(L<=mid)
upmin(res,query(ls[p],L,R,l,mid));
if(R>mid)
upmin(res,query(rs[p],L,R,mid+1,r));
return res;
}
}
struct cmp
{
int operator ()(int a,int b) const
{
return d[a]<d[b];
}
};
priority_queue<int,vector<int>,cmp> q;
int ans[100010];
int k;
int main()
{
open("a");
int n;
scanf("%d",&n);
int i,x,y;
for(i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
g[x].push_back(y);
g[y].push_back(x);
}
dfs(1,0,1);
for(i=1;i<=n;i++)
c[s[i]].push_back(i);
for(i=1;i<maxd;i++)
{
k=i;
int res=leaves;
for(auto v:c[i])
q.push(v);
seg::rt=seg::n=0;
while(!q.empty())
{
x=q.top();
q.pop();
if(s[x]<=k-1)
continue;
if(seg::query(seg::rt,st[x],ed[x],1,n)<=d[x]+k-1)
continue;
seg::add(seg::rt,st[x],d[x],1,n);
res++;
if(d[x]>k)
q.push(jump(x,k));
}
ans[i]=res;
}
int q;
scanf("%d",&q);
while(q--)
{
scanf("%d",&x);
if(x>=maxd)
printf("%d\n",leaves);
else
printf("%d\n",ans[x]);
}
return 0;
}

【XSY2667】摧毁图状树 贪心 堆 DFS序 线段树的更多相关文章

  1. BZOJ_4034 [HAOI2015]树上操作 【树链剖分dfs序+线段树】

    一 题目 [HAOI2015]树上操作 二 分析 树链剖分的题,这里主要用到了$dfs$序,这题比较简单的就是不用求$lca$. 1.和树链剖分一样,先用邻接链表建双向图. 2.跑两遍$dfs$,其实 ...

  2. [bzoj3306]树——树上倍增+dfs序+线段树

    Brief Description 您需要写一种数据结构,支持: 更改一个点的点权 求一个子树的最小点权 换根 Algorithm Design 我们先忽略第三个要求. 看到要求子树的最小点权,我们想 ...

  3. BZOJ - 4196 软件包管理器 (树链剖分+dfs序+线段树)

    题目链接 设白色结点为未安装的软件,黑色结点为已安装的软件,则: 安装软件i:输出结点i到根的路径上的白色结点的数量,并把结点i到根的路径染成黑色.复杂度$O(nlog^2n)$ 卸载软件i:输出结点 ...

  4. 【BZOJ-3252】攻略 DFS序 + 线段树 + 贪心

    3252: 攻略 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 339  Solved: 130[Submit][Status][Discuss] D ...

  5. Codeforces Round #442 (Div. 2)A,B,C,D,E(STL,dp,贪心,bfs,dfs序+线段树)

    A. Alex and broken contest time limit per test 2 seconds memory limit per test 256 megabytes input s ...

  6. BZOJ 3252题解(贪心+dfs序+线段树)

    题面 传送门 分析 此题做法很多,树形DP,DFS序+线段树,树链剖分都可以做 这里给出DFS序+线段树的代码 我们用线段树维护到根节点路径上节点权值之和的最大值,以及取到最大值的节点编号x 每次从根 ...

  7. dfs序线段树

    dfs序+线段树,啥?如果在一棵树上,需要你修改一些节点和查询一些节点,如果直接dfs搜的话肯定超时,那用线段树?树结构不是区间啊,怎么用?用dfs序将树结构转化为一个区间,就能用线段树进行维护了. ...

  8. DFS序+线段树(bzoj 4034)

    题目链接 题目就不多说了. 本题目,可以用dfs序+线段树做:题目给定了一棵树,树上节点告诉了权值.我们可以先将这棵树进行dfs将一棵树变成线性结构:如图 变成这样后,然后就可以用线段树. 操作1:也 ...

  9. [51nod 1681]公共祖先(dfs序+线段树合并)

    [51nod 1681]公共祖先(dfs序+线段树合并) 题面 给出两棵n(n<=100000)个点的树,对于所有点对求它们在两棵树中公共的公共祖先数量之和. 如图,对于点对(2,4),它们在第 ...

随机推荐

  1. 基于node.js的博客搭建

    一个博客应当具备哪些功能? 前台展示 点击下一页,可以点击分类导航. 可以点击进入到具体博文页面 下方允许评论.显示发表时间.允许留言分页. 右侧有登录注册界面. 后台管理 管理员账号:登陆后看到页面 ...

  2. Python-生成器_36

    #生成器函数 def generator(): print(1) return 'a' ret = generator() print(ret) #只要含有yield关键字的函数都是生成器函数 # y ...

  3. c++入门之命名空间存在的意义

    看过鸡啄米的C++编程入门系列教程的朋友,应该能注意到,在其中的很多实例中,都有这么一条语句:using namespace std;,即使用命名空间std,其作用就是规定该文件中使用的标准库函数都是 ...

  4. p151开映射札记

    1. 如何理解这句话? 2.连续有什么用? 3.为什么区间包含,经过算子T还是包含? 谢谢 谢谢学长 我懂了  1.2.     3有点儿模糊 1.连续等价于开集原像是开集,而可逆算子的逆的原像就是的 ...

  5. Innodb 实现高并发、redo/undo MVCC原理

    一.并发控制   因为并发情况下有可能出现不同线程对同一资源进行变动,所以必须要对并发进行控制以保证数据的同一与安全.   可以参考CPython解释器中的GIL全局解释器锁,所以说python中没有 ...

  6. mysql备份(导出)数据库,并恢复数据

    导出某个数据库数据到文件中 假设要导出test这个数据库,那么可以在Linux命令行(不是在mysql中) [root@ubuntu /data]# mysqldump -uroot -p test ...

  7. HDU 3947 Assign the task

    http://acm.hdu.edu.cn/showproblem.php?pid=3974 Problem Description There is a company that has N emp ...

  8. laravel自定义门面

    https://learnku.com/articles/19195   关于laravel门面和服务提供者使用的一点见解,门面之词,不足之处,还请多多指教. 在laravel中,我们可能需要用到自己 ...

  9. null值经过强转会怎样?

    null还是null,类型不会改变的,也不会报错!

  10. 1、通过eureka创建注册中心

    第一个demo(用户需要调用电影服务) 1.创建项目 new starter project 勾选上Eureka Server 2.编写application.yml #配置端口 server: po ...