G. Gangsters in Central City

给出一棵$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+10;
struct rec{ int pre,to; }a[N<<1];
int n,Q,dfn[N],tot,head[N],g[N][25],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[10];
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[0]; g[u][0]=fa,dep[u]=dep[fa]+1; size[u]=0;
    bool leaf=1;
    for (int i=head[u];i;i=a[i].pre){
        int v=a[i].to; if (v==fa) continue;
        dfs1(v,u); leaf=0; size[u]+=size[v];
    }
    if (leaf) size[u]=1;
}
void dfs2(int u,int fa,int rt)
{
    bool leaf=1;
    for (int i=head[u];i;i=a[i].pre) {
        int v=a[i].to; if (v==fa) continue;
        dfs2(v,u,rt); leaf=0;
    }
    if (leaf) zs[u]=rt;
}
void init() {
    for (int i=1;i<=21;i++)
     for (int j=1;j<=n;j++)
      g[j][i]=g[g[j][i-1]][i-1];
}
int lca(int u,int v)
{
    if (dep[u]<dep[v]) swap(u,v);
    for (int i=21;i>=0;i--)
     if (dep[g[u][i]]>=dep[v]) u=g[u][i];
    if (u==v) return u;
    for (int i=21;i>=0;i--)
     if (g[u][i]!=g[v][i]) u=g[u][i],v=g[v][i];
    return g[u][0];
}
int main()
{
    freopen("gangsters.in","r",stdin);
    freopen("gangsters.out","w",stdout);
    scanf("%d%d",&n,&Q);
    for (int i=2;i<=n;i++) {
        int u; scanf("%d",&u);
        adde(i,u); adde(u,i);
    }
    dfs1(1,0);init();
    for (int i=head[1];i;i=a[i].pre) dfs2(a[i].to,1,a[i].to);
    int ans1=0,ans2=0,ret=0;
    while (Q--) {
        int d; scanf("%s%d",s,&d);
        if (s[0]==‘+‘) {
            ret++;
            if (cnt[zs[d]]==0) ans1++;  cnt[zs[d]]++;
            int pre=0;
            if (st[zs[d]].size()!=0) {
                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]]==1) 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=0;
            if (st[zs[d]].size()!=0) {
                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 0;
}

原文地址:https://www.cnblogs.com/ljc20020730/p/11260473.html

时间: 2024-10-30 05:41:11

G. Gangsters in Central City的相关文章

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

G.Gangsters in Central City 题意:一棵树,节点1为根,是水源.水顺着边流至叶子.该树的每个叶子上有房子.有q个询问,一种为房子u被强盗入侵,另一种为强盗撤离房子u.对于每个询问,要求给出最小的阀门数来阻断水流向强盗所在房子,且在阀门数最小的情况下求最小的误伤房子数(即没被入侵却被断水的房子). 思路:观察可发现,与根相连的子树都是独立的,因此有每有一颗这样的子树里有强盗,则ans1++,每有一颗这样的子树强盗全部撤离则ans1--:因此要维护的是误伤数ans2,我们对

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

题意:现在有一棵树,1号节点是水源,叶子节点是村庄,现在有些怪兽会占领一些村庄(即只占领叶子节点),现在要割去一些边,使得怪兽到不了水源.给出怪兽占领和离开的情况,现在要割每次回答最小的割,使得怪兽不与1号节点有联系,而且满足被阻隔的村庄最少.输出最小割与组少的被误伤的村庄. 思路:把与一号节点相邻的点看作祖先gfa,然后它们自己作为树的根节点,根节点保存了子树里叶子节点的个数.很显然一棵树我们要割的是这棵树里所有怪兽的LCA与父亲边.子数里所有怪兽的LCA=LCA(最小DFS序的怪兽点,最大D

【UVa 208】Firetruck

The Center City ?re department collaborates with the transportation department to maintain mapsof the city which re?ects the current status of the city streets. On any given day, several streets areclosed for repairs or construction. Fire?ghters need

UVA Firetruck (DFS)

The Center City fire department collaborates with the transportation department to maintain maps of the city which reflects the current status of the city streets. On any given day, several streets are closed for repairs or construction. Firefighters

读书笔记之《The Art of Readable Code》Part 3

如何重新组织代码提高可读性? (函数层面, part 3)1. 抽取与主要问题无关的代码2. 重新组织代码使得一次只做一件事3. 首先描述功能,然后再实现功能,这样更清楚明了 如何抽出问题无关的子问题? (chap 10)0. 无关问题的思考 - 看到一个函数或一个代码块, 问自己, "这段代码的高层作用是什么(high-level gloal)" - 对于每一行代码, 思考"它是直接解决这个目标吗",还是"解决一个子问题来达到目标的解决" -

MySQL外键关联(创世纪新篇)

数据库外键 01.mysql> show create table country\G  02.*************************** 1. row ***************************  03.       Table: country  04.Create Table: CREATE TABLE `country` (  05.  `country_id` smallint(5) unsigned NOT NULL auto_increment,  06. 

《Entity Framework 6 Recipes》中文翻译系列 (18) -----第三章 查询之结果集扁平化和多属性分组

翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 3-14  结果集扁平化 问题 你有一对多关联的两个实体,你想通过一个查询,获取关联中的两个实体的扁平化投影.扁平化或者叫压缩,这是不规范的叫法.它是指一个有父类和子类的对象图,被投影到一个单独的类中. 解决方案 假设你有一对拥有一对多关联的实体,如图3-15所示的模型. 图3-15 模型中,一个代表助理的Associate的实体类型和一个代表助理工资历史的AssociateSalary实体

Elasticsearch Java API(八)--搜索有相同父id的子文档

需求: 搜索具有相同父id的所有子文档. 数据:    mapping:      { "mappings": { "branch": {}, "employee": { "_parent": { "type": "branch" } } } } 父文档: { "index": { "_id": "london" }} { &q

HDU4081 Qin Shi Huang&#39;s National Road System【prim最小生成树+枚举】

先求出最小生成树,然后枚举树上的边,对于每条边"分别"找出这条割边形成的两个块中点权最大的两个 1.由于结果是A/B,A的变化会引起B的变化,两个制约,无法直接贪心出最大的A/B,故要通过枚举 2.不管magic road要加在哪里,加的边是否是最小生成树上的边,都会产生环,我们都要选择一条边删掉 注意删掉的边必须是树的环上的边,为了使结果最大,即找出最大的边 3.可以枚举两点,找出边,也可以枚举边,找出点,我是用后者,感觉比较容易写也好理解 #include <cstdio&g