小H和游戏——dfs序+树状数组 | 思维

题目链接:https://ac.nowcoder.com/acm/contest/5203/D

题目大意:

给你一棵n个节点的树,然后有q次轰炸,每次轰炸可以波及到与当前节点距离小于等于2的节点,并输出该次轰炸后当前节点的被轰炸次数。

题解:

解法一:树状数组(线段树)+dfs序

这个题先讲相对而言时间复杂度比较高的解法,但是比较好想,容易理解。

首先我们可以求出每个点的儿子编号的区间(类似于dfs序的操作)以及每个节点的编号。

求出区间后就简单了,考虑每次轰炸,只把贡献传给儿子和父亲,这样统计的时候查询当前节点的值(即轰炸儿子和父亲产生的贡献),父亲节点的值(即轰炸爷爷节点和兄弟节点产生的贡献)以及儿子区间的值(轰炸孙子节点产生的贡献)

经典的区间修改,区间查询操作,推荐用树状数组维护(常数小)

trick:有一部分多考虑了,需要减去,代码中有解释。

#pragma GCC optimize(2)
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<queue>
#define PI atan(1.0)*4
#define E 2.718281828
#define rp(i,s,t) for (register int i = (s); i <= (t); i++)
#define RP(i,t,s) for (register int i = (t); i >= (s); i--)
#define ll long long
#define ull unsigned long long
#define mst(a,b) memset(a,b,sizeof(a))
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
#define debug printf("ac\n");
using namespace std;
inline int read()
{
    int a=0,b=1;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        if(c=='-')
            b=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        a=(a<<3)+(a<<1)+c-'0';
        c=getchar();
    }
    return a*b;
}
const int INF = 0x3f3f3f3f;
const int N = 750005;
vector<int> G[N];
int id[N],l[N],r[N],idx;
int son[N],pa[N];
ll c1[N],c2[N],a[N];
inline int lowbit(int x){return x&(-x);}
inline void _add(int x,int val){for(int i=x;i<N;i+=lowbit(i)) c1[i]+=val,c2[i]+=1ll*val*x;}
inline void add(int l,int r,int val){_add(l,val);_add(r+1,-val);}
inline ll _query(int x){ll res=0;for(int i=x;i;i-=lowbit(i)) res+=1ll*c1[i]*(x+1)-c2[i];return res;}
inline ll query(int l,int r){return _query(r)-_query(l-1);}
void dfs(int x,int f){//求出儿子的编号区间以及当前节点的编号
    pa[x]=f;son[x]=int(G[x].size())-(pa[x]?1:0);
    if(son[x]) l[x]=idx+1;
    for(int y:G[x]) if(y!=f) id[y]=++idx;
    if(son[x]) r[x]=idx;
    for(int y:G[x]) if(y!=f) dfs(y,x);
}
int n,q,u,v,x;
int main(){
    n=read(),q=read();
    rp(i,1,n-1) u=read(),v=read(),G[u].pb(v),G[v].pb(u);
    id[1]=++idx;
    dfs(1,0);
    while(q--){
        x=read();
        ll res=query(id[x],id[x])+a[x]+1;
        if(son[x]) res+=query(l[x],r[x]),add(l[x],r[x],1);
        if(pa[x]) res+=query(id[pa[x]],id[pa[x]]),add(id[pa[x]],id[pa[x]],1);
        a[x]-=int(G[x].size())-1;//减去多余的,即自身轰炸与传给儿子多余的部分
        //考虑第一次自身轰炸传给儿子后,再进行自身轰炸时统计儿子贡献时多余的部分(即儿子个数)
        printf("%lld\n",res); 
    }
    return 0;
}

解法二:思维

首先我们不难发现对一个节点产生贡献的节点有6种,自身,儿子,孙子,兄弟,父亲,爷爷。

父亲节点,爷爷节点比较好办,因为就一个父亲和爷爷。

那儿子和孙子节点怎么处理呢?先不考虑最难的兄弟节点

那就不处理呗,每次只是暴力地把贡献上传给父亲和爷爷。

这样对于父亲和爷爷来说,他们儿子和孙子的节点贡献事先已经求出来了。

然后考虑最难的兄弟节点的贡献。

我们设cnt[i][0/1/2]表示一次轰炸后,自己/儿子/孙子波及到i点且还剩距离j可以波及的轰炸的次数

兄弟的贡献,一定是保存在了cnt[fa[i]][1]当中的,不过,里面有一个是自己波及的,所以答案加上cnt[fa[i]][1]-cnt[x][2]即可

代码实现:

#pragma GCC optimize(2)
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<queue>
#include<bits/stdc++.h>
#define PI atan(1.0)*4
#define E 2.718281828
#define rp(i,s,t) for (register int i = (s); i <= (t); i++)
#define RP(i,t,s) for (register int i = (t); i >= (s); i--)
#define ll long long
#define ull unsigned long long
#define mst(a,b) memset(a,b,sizeof(a))
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pii pair<int,int>
#define pb push_back
#define debug printf("ac\n");
using namespace std;
inline int read()
{
    int a=0,b=1;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        if(c=='-')
            b=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        a=(a<<3)+(a<<1)+c-'0';
        c=getchar();
    }
    return a*b;
}
const int N = 1e6+7;
int cnt[N][3];
int fa[N];
int u,v,num,n,query;
int main(){
    n=read(),query=read();
    for(int i=1;i<=n-1;i++){
        u=read(),v=read();
        fa[v]=u;
    }
    while(query--){
        num=read();
        cnt[num][2]++;//自身
        cnt[fa[num]][1]++;cnt[fa[fa[num]]][0]++;//上传对父亲和爷爷的贡献
        int ans=cnt[num][2]+cnt[num][1]+cnt[num][0];//自身+儿子+孙子对当前节点的贡献
        ans+=cnt[fa[num]][2];//父亲节点的贡献
        ans+=cnt[fa[num]][1]-cnt[num][2];//兄弟节点的贡献
        ans+=cnt[fa[fa[num]]][2];//爷爷节点的贡献
        printf("%d\n",ans);
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值