给出一棵$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. nginx+gunicorn/uwsgi+python web 的前世今生

    我们在部署 flask.django 等 python web 框架时,网上最多的教程就是 nginx+gunicorn/uwsgi 的部署方式,那为什么要这么部署呢,本文就来系统地解释这个问题. 必 ...

  2. JavaSE--jdbc编程

    JDBC全称为:Java Data Base Connectivity (java数据库连接),可以为多种数据库提供统一的访问.JDBC是sun开发的一套数据库访问编程接口,是一种SQL级的API.它 ...

  3. 提升scrapy的爬取效率

    增加并发: 默认scrapy开启的并发线程为32个,可以适当进行增加.在settings配置文件中修改CONCURRENT_REQUESTS = 100值为100,并发设置成了为100. 降低日志级别 ...

  4. Java开发者想尝试转行大数据,学习方向建议?

      ​前言 相信很多Java开发者都对大数据有一定的了解,随着大数据时代的到来,也有很多Java程序员想要转行大数据.大数据技术中大多数平台使用的都是Java语言,因此,对于大数据技术的学习来说,Ja ...

  5. Date对象中的方法

    特殊说明:设置时间的方法,虽然W3C说明传参的范围,在开发过程中,传入的参数不在该范围也是可以的.例如: var t = new Date(), d = t.getDate(); //当天时间往前推2 ...

  6. 多线程的些许理解(平台x86,具体考虑linux,windows)

    多线程的些许理解 一.体系架构 1.原子操作 1) 定义 不可中断的一个或者一系列操作,也就是不会被线程调度机制打断的操作,在运行期间不会有任何的上下文切换(context switch). 2) 我 ...

  7. Delphi MaskEdit 组件

  8. Ceph实战入门之安部署篇

    最近Ceph官方发布了luminous长久支持版,新版本增加了很多有意思的功能,但是入门还是先从部署安装开始. 环境说明 在Win10下安装VMware® Workstation 12 Pro软件,用 ...

  9. PAT Basic 1010 一元多项式求导 (25 分)

    给定一句英语,要求你编写程序,将句中所有单词的顺序颠倒输出. 输入格式: 测试输入包含一个测试用例,在一行内给出总长度不超过 80 的字符串.字符串由若干单词和若干空格组成,其中单词是由英文字母(大小 ...

  10. 04-spring框架—— Spring 集成 MyBatis

    将 MyBatis与 Spring 进行整合,主要解决的问题就是将 SqlSessionFactory 对象交由 Spring来管理.所以,该整合,只需要将 SqlSessionFactory 的对象 ...