题目大意:
给你一棵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; }
小H和游戏——dfs序+树状数组 | 思维
最新推荐文章于 2021-08-19 21:29:57 发布