给出一棵$1$为根节点的含$n$个节点的树,叶子节点都是房屋,在一个集合里面添加房屋和移除房屋。

每一次添加和移除后,回答下面两个问题。

1.  使得已选房屋都不能从根节点到达,最少需要砍多少条边。

2.  在第$1$问最少砍去边的条件下,如何砍边使得从节点点开始走不能到达的非已选房屋数目最小,输出最小值。

对于100%的数据 , $2 ≤ n ≤ 10^5 , 1 ≤ q ≤ 10^5$

Solution :

  首先观察到,第一问的答案。

  非常容易证明的一个上边界是砍去所有合法的$u$和1的连边,其中合法的$u$表示既和$1$直接相连,且含有有已选被选叶子节点的$1$的直接儿子$u$

  显然,这样构造是可以切断所有的已选房屋的。

  如果不那么取,在一棵有被选叶子的子树中,如果不像上面一样构造,那么为了切断已选叶子,切边必须下移,显然,为了保证该子树已选叶子被截断,那么断边的数目必须递增。

  所有对于第一问,贪心策略成立。

  在第一问思考的基础上,我们进一步想,如果找到一个临界边,若砍掉其下面的边使得被选叶子节点被切断这些边数大于1了,而砍掉上面的那条边却恰好可以切断被选叶子节点,那么问题就解决了。

  这个临界边是这个子树里面被选叶子节点集合的LCA与父亲那一条连边。

  这就是第二问的贪心策略。

  如何求一个点集在树上的LCA(深度最大的公共祖先)呢?

  结论是:求出这个点集dfs序最小的一个点$u$,dfs序最大的一个点$v$,则$lca(u,v)$即为所求。

  这是因为dfs序覆盖的范围是一个连续的区间。

  假设有三个点,DFS序分别为a,p,b且a<p<b。那么我们断言LCA(a,p,b)=LCA(a,b)。

  这是因为:两个点的LCA,就是最近的同时包含它们的那个区间所代表的点。既然LCA(a,b)的区间包含了a和b,那也一定包含了位于a和b之间的p。

  因此,对于每任意三个点,只需要保存DFS序最大和最小的点。这个点集的LCA,也就是整个点集DFS序最小的和DFS序最大的这两个点的LCA。

至此,本题的理论部分就结束了,接下去就是实现的问题了。

首先对于每个叶子求出它对应属于根节点的那个子树zs[u],对于每个节点求出其size[]表示含有叶子节点的数目。

ret表示当前被标记的叶子节点数目,ans1表示第一问的答案,ans2表示被上述贪心策略覆盖总点数。

显然,最后的答案是ans1, ans2 - ret;

对于每个根节点子树开个set,维护当中被标记节点的dfs序,每次只需要求最大和最小即可。

对于每个+和-分别维护就行了(动态更新ans1,ans2)。

复杂度是$O(n log_2 n)$

# include <bits/stdc++.h>
using namespace std;
const int N=1e5+;
struct rec{ int pre,to; }a[N<<];
int n,Q,dfn[N],tot,head[N],g[N][],dep[N],size[N],zs[N],cnt[N];
struct node{ int x;};
bool operator < (node a,node b){return dfn[a.x]<dfn[b.x];}
set<node>st[N];
char s[];
void adde(int u,int v)
{
a[++tot].pre=head[u];
a[tot].to=v;
head[u]=tot;
}
void dfs1(int u,int fa)
{
dfn[u]=++dfn[]; g[u][]=fa,dep[u]=dep[fa]+; size[u]=;
bool leaf=;
for (int i=head[u];i;i=a[i].pre){
int v=a[i].to; if (v==fa) continue;
dfs1(v,u); leaf=; size[u]+=size[v];
}
if (leaf) size[u]=;
}
void dfs2(int u,int fa,int rt)
{
bool leaf=;
for (int i=head[u];i;i=a[i].pre) {
int v=a[i].to; if (v==fa) continue;
dfs2(v,u,rt); leaf=;
}
if (leaf) zs[u]=rt;
}
void init() {
for (int i=;i<=;i++)
for (int j=;j<=n;j++)
g[j][i]=g[g[j][i-]][i-];
}
int lca(int u,int v)
{
if (dep[u]<dep[v]) swap(u,v);
for (int i=;i>=;i--)
if (dep[g[u][i]]>=dep[v]) u=g[u][i];
if (u==v) return u;
for (int i=;i>=;i--)
if (g[u][i]!=g[v][i]) u=g[u][i],v=g[v][i];
return g[u][];
}
int main()
{
freopen("gangsters.in","r",stdin);
freopen("gangsters.out","w",stdout);
scanf("%d%d",&n,&Q);
for (int i=;i<=n;i++) {
int u; scanf("%d",&u);
adde(i,u); adde(u,i);
}
dfs1(,);init();
for (int i=head[];i;i=a[i].pre) dfs2(a[i].to,,a[i].to);
int ans1=,ans2=,ret=;
while (Q--) {
int d; scanf("%s%d",s,&d);
if (s[]=='+') {
ret++;
if (cnt[zs[d]]==) ans1++; cnt[zs[d]]++;
int pre=;
if (st[zs[d]].size()!=) {
int u=(*(st[zs[d]].begin())).x,v=(*(--st[zs[d]].end())).x;
pre=size[lca(u,v)];
}
st[zs[d]].insert((node){d});
int u=(*(st[zs[d]].begin())).x,v=(*(--st[zs[d]].end())).x;
int now=size[lca(u,v)];
ans2+=now-pre; printf("%d %d\n",ans1,ans2-ret);
} else {
ret--;
if (cnt[zs[d]]==) ans1--; cnt[zs[d]]--;
int u=(*(st[zs[d]].begin())).x,v=(*(--st[zs[d]].end())).x;
int pre=size[lca(u,v)];
st[zs[d]].erase((node){d});
int now=;
if (st[zs[d]].size()!=) {
u=(*(st[zs[d]].begin())).x,v=(*(--st[zs[d]].end())).x;
now=size[lca(u,v)];
}
ans2+=now-pre; printf("%d %d\n",ans1,ans2-ret);
}
}
return ;
}

G. Gangsters in Central City的更多相关文章

  1. 2016 NEERC, Northern Subregional Contest G.Gangsters in Central City(LCA)

    G.Gangsters in Central City 题意:一棵树,节点1为根,是水源.水顺着边流至叶子.该树的每个叶子上有房子.有q个询问,一种为房子u被强盗入侵,另一种为强盗撤离房子u.对于每个 ...

  2. Codeforces Gym 101142 G Gangsters in Central City (lca+dfs序+树状数组+set)

    题意: 树的根节点为水源,编号为 1 .给定编号为 2, 3, 4, …, n 的点的父节点.已知只有叶子节点都是房子. 有 q 个操作,每个操作可以是下列两者之一: + v ,表示编号为 v 的房子 ...

  3. Gym 101142G : Gangsters in Central City(DFS序+LCA+set)

    题意:现在有一棵树,1号节点是水源,叶子节点是村庄,现在有些怪兽会占领一些村庄(即只占领叶子节点),现在要割去一些边,使得怪兽到不了水源.给出怪兽占领和离开的情况,现在要割每次回答最小的割,使得怪兽不 ...

  4. Gym101142G Gangsters in Central City

    题目链接:https://cn.vjudge.net/problem/Gym-101142G 知识点: DFS序.LCA 题目大意: 给定一棵有根树(根为 \(1\)).每次修改叶子节点会被染成黑色( ...

  5. 2016-2017 ACM-ICPC, NEERC, Northern Subregional Contest

    A. Anniversary Cake 随便挑两个点切掉就好了. #include<bits/stdc++.h> using namespace std; const int Maxn=2 ...

  6. uva208 - Firetruck

    Firetruck The Center City fire department collaborates with the transportation department to maintai ...

  7. UVA208-Firetruck(并查集+dfs)

    Problem UVA208-Firetruck Accept:1733  Submit:14538 Time Limit: 3000 mSec  Problem Description The Ce ...

  8. 五、Pandas玩转数据

    Series的简单运算 import numpy as np import pandas as pd s1=pd.Series([1,2,3],index=['A','B','C']) print(s ...

  9. [转]MySQL Connector/C++(一)

    http://www.cnblogs.com/dvwei/archive/2013/04/18/3029464.html#undefined#undefined MySQL Connector/C++ ...

随机推荐

  1. 查找担保圈-step1-担保圈表函数

    USE [test]; GO /****** Object: UserDefinedFunction [dbo].[f_findrecycle] Script Date: 2019/7/8 14:37 ...

  2. 第二篇 jQuery 选择器

    2-1,2  table隔行变色 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "h ...

  3. Nginx(高并发)

    Nginx(engine x)高性能和反向代理的web服务器反向代理:保护客户资源,只要是http协议都可以Web服务器:IIS 阿帕奇 NginxNginx可以作为负载均衡(NLB只支持Http)我 ...

  4. luogu题解 P1707 【刷题比赛】矩阵加速递推

    题目链接: https://www.luogu.org/problemnew/show/P1707 分析: 洛谷的一道原创题,对于练习矩阵加速递推非常不错. 首先我们看一下递推式: \(a[k+2]= ...

  5. luogu P4437 [HNOI/AHOI2018]排列

    luogu 问题本质是把\(a_i\)作为\(i\)的父亲,然后如果有环就不合法,否则每次要取数,要满足取之前他的父亲都被取过(父亲为0可以直接取),求最大价值 贪心想法显然是要把权值大的尽量放在后面 ...

  6. LintCode 29---交叉字符串

    public class Solution { /** * @param s1: A string * @param s2: A string * @param s3: A string * @ret ...

  7. Centos7下安装ZooKeeper

    1.下载源码 zookeeper 需要jdk的支持,需要先安装jdk 官网下载地址: http://mirrors.hust.edu.cn/apache/zookeeper/ 选择最新的版本进行下载 ...

  8. eclipses配置tomcat

    1,项目右键属性,设置为1.8,与jdk相对应 2,自动发布,tomcat 3,使用自己的tomcat 4,

  9. 多线程编程-- part 4 线程间的通信

    线程间的相互作用 线程之间需要一些协调通信,来共同完成一件任务. Object类相关的方法:notify(),notifyAll(),wait().会被所有的类继承,这些方法是final不能被重写.他 ...

  10. kafka核心原理总结

    新霸哥发现在新的技术发展时代,消息中间件也越来越受重视,很多的企业在招聘的过程中着重强调能够熟练使用消息中间件,所有做为一个软件开发爱好者,新霸哥在此提醒广大的软件开发朋友有时间多学习. 消息中间件利 ...