浙江理工大学2019年程序设计校赛 问题 I: 文明(dfn建树打标记)

题目链接:1835: 文明

题目描述

整个游戏地图是n个结点的树,要在这个地图上进行q次游戏,每次有k个玩家,每个玩家的国家一开始的领土只有一个点a1,a2,...,ak ,保证每个点两两不同。然后1,2,...,k号玩家轮流进行一个回合,每个回合可以对国家疆土上的所有节点进行距离为1 的扩展,如果扩展到不属于任何其他国家的节点,则将这个点划入自己国家的疆土。如此往复,直到所有的节点都被某个国家占领。 快快最近沉迷于《文明》无法自拔,他想问问你他的国家能占领多大的游戏地图。由于快快是丁总特邀的黄金会员,所以他每次都是1号玩家,即他每次都是第一个进行回合的。 

输入

第一行输入两个整数n,q ,分别表示游戏地图的节点数和游戏数。 

接下来n-1行,每行输入两个整数x,y,表示游戏地图中有连边x,y,保证游戏地图是一棵无重边无自环的树。 

接下来q行,每行先输入一个整数ki ,表示第i 局游戏有ki个玩家。 

接下来ki个数aij,表示这局游戏第j个玩家的国家初始所在的节点。 

对于所有数据,有n,q<=500000,1<=ki<=n,所有ki的和<=1000000(i从1到q) 。 

输出

输出q行q个数,表示每次游戏快快的国家能占领的节点数。

样例输入

6 4
1 2
1 3
2 4
3 5
3 6
2 1 3
3 1 4 5
3 4 5 6
3 1 2 3

样例输出

3
4
3
1

提示

样例解释 1

第一局游戏快快一开始在1号点,第一时刻占领了2号点,第二时刻占领了4号点。  

第二局游戏快快一开始在1号点,第一时刻占领了2号点和3号点,第二时刻占领了6号点。 第四局游戏快快一开始在1号点,然后没有其他可以占领的点。 

题目分析:

首先选择一个节点作为根,对树节点预处理欧拉序,以及lca相关的深度和父亲集合。

对dfn序建立一棵标记线段树,每个最小区间表示这个节点是否被标记,被标记为1,否则为0.

我们将不是一号玩家(编号记为rt)所占领的节点标记为1,考虑每一个其他玩家a[i](i>1)能为树上的哪些点做标记。

以下用a表示当前节点a[i]:

a与rt的相对关系可以分为以下三种(红色部分为需要标记的部分):

根据游戏规则我们就能知道rt和a之间的距离会被分成两半。如果找到一个u恰好是被a占领的,那么这个u到rt的距离大于u到a的距离,如果找到一个u恰好是不被a占领的,那么这个u到rt的距离小于u到a的距离。那么就可以分成以上三种情况,其中第三种情况还包括u和lca(rt,a)重合。通过计算距离找到u的位置然后分情况给需要打标记的点都打上标记即可。

//u的子树范围[dfn[u],low[u]],非u子树的范围[1,dfn[u])U(low[u],n]

代码:数据量大需要使用快读

#include <bits/stdc++.h>
#define ll long long
using namespace std;
namespace _{
    char buf[100000], *p1 = buf, *p2 = buf; bool rEOF = 1;//为0表示文件结尾
    inline char nc(){ return p1 == p2 && rEOF && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? (rEOF = 0, EOF) : *p1++; }
    template<class _T>
    inline void read(_T &num){
        char c = nc(), f = 1; num = 0;
        while (c<'0' || c>'9')c == '-' && (f = -1), c = nc();
        while (c >= '0'&&c <= '9')num = num * 10 + c - '0', c = nc();
        num *= f;
    }
    inline bool need(char &c){ return c >= 'a'&&c <= 'z' || c >= '0'&&c <= '9' || c >= 'A'&&c <= 'Z'; }//读入的字符范围
    inline void read_str(char *a){
        while ((*a = nc()) && !need(*a) && rEOF);   ++a;
        while ((*a = nc()) && need(*a) && rEOF)++a; --p1, *a = '\0';
    }
    template<class _T>
    inline void println(_T x){
        static int buf[30], len; len = 0; do buf[len++] = x % 10, x /= 10; while (x);
        while (len) putchar(buf[--len] + 48);
        putchar('\n');
    }
}using namespace _;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9+7;
const int MAXN = 500005;

int n, q, k;
int a[MAXN];

struct edge{
    int v,next;
}e[MAXN<<1];
int head[MAXN], cnt;
inline void add(int u,int v){
    e[cnt] = (edge){v,head[u]}, head[u] = cnt++;
}

int dep[MAXN], low[MAXN], dfn[MAXN], tot, fa[MAXN][20];
void dfs(int u,int f){
    dep[u] = dep[f] + 1, dfn[u] = ++tot, fa[u][0] = f;
    for(int i = 1;i<20;i++)fa[u][i] = fa[fa[u][i-1]][i-1];
    for(int i = head[u];~i;i = e[i].next){
        if(e[i].v == f)continue;
        dfs(e[i].v, u);
    }
    low[u] = tot;
}
inline int lca(int u,int v){
    if(dep[u]<dep[v])swap(u,v);
    for(int i = 0, del = dep[u]-dep[v];del;i++, del>>=1)
        if(del&1)u = fa[u][i];
    if(u == v)return u;
    for(int i = 19;i>=0;i--)
        if(fa[u][i]!=fa[v][i])u = fa[u][i], v = fa[v][i];
    return fa[u][0];
}
#define mid ((l+r)>>1)
struct Tree{
    int lazy, sum;
}T[MAXN<<2];
inline void pushdown(int u,int l,int r){
    if(~T[u].lazy){
        T[u<<1].lazy = T[u<<1|1].lazy = T[u].lazy;
        T[u<<1].sum = T[u].lazy*(mid-l+1);
        T[u<<1|1].sum = T[u].lazy*(r-mid);
        T[u].lazy = -1;
    }
}
void update(int u,int l,int r,int L,int R,int x){
    if(L<=l&&r<=R){
        T[u].lazy = x;
        T[u].sum = x*(r-l+1);
        return;
    }
    pushdown(u,l,r);
    if(L<=mid)update(u<<1,l,mid,L,R,x);
    if(R>mid)update(u<<1|1,mid+1,r,L,R,x);
    T[u].sum = T[u<<1].sum + T[u<<1|1].sum;
}
int main() {
    memset(head,-1,sizeof head);
    read(n), read(q);
    for(int i = 1,u,v;i<n;i++){
        read(u), read(v), add(u,v), add(v,u);
    }
    dfs(1,1);
    while(q--){
        read(k); for(int i = 1;i<=k;i++) read(a[i]);
        if(k == 1){
            println(n); continue;
        }
        T[1].lazy = 0;
        int rt = a[1];
        for(int i = 2;i<=k;i++){
            if(dfn[rt] <= dfn[a[i]] && dfn[a[i]] <= low[rt]){   //情况1,在rt的子树中
                int d = ((dep[a[i]] + dep[rt])>>1) + 1, u = a[i];
                for(int j = 19;j>=0;j--)
                    if(dep[fa[u][j]] >= d) u = fa[u][j];
                update(1,1,n,dfn[u],low[u],1);        //对u整个子树打标记
            }else{
                int lc = lca(rt, a[i]);
                int dis = ((dep[a[i]] + dep[rt] - (dep[lc]<<1))>>1)+1;
                if(dep[a[i]] >= dep[rt]){                        //情况2
                    int d = dis - dep[rt] + (dep[lc]<<1), u = a[i];
                    for(int j = 19;j>=0;j--)
                        if(dep[fa[u][j]] >= d) u = fa[u][j];
                    update(1,1,n,dfn[u],low[u],1);    //对u整个子树打标记
                }else{                                           //情况3
                    int d = dep[rt] - dis + 1, u = rt;    //d表示u的深度
                    for(int j = 19;j>=0;j--)
                        if(dep[fa[u][j]] >= d) u = fa[u][j];
                    if(1 <= dfn[u]-1)update(1,1,n,1,dfn[u]-1,1); //对u子树外的点打标记
                    if(low[u]+1 <= n)update(1,1,n,low[u]+1,n,1); //对u子树外的点打标记
                }
            }
        }
        println(n-T[1].sum);    //答案为总点数-被标记点数
    }
    return 0;
}

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值