题目链接:点击查看
题目大意:给出n棵树,再给出m个询问,每次询问给出两个整数u和k,先假设u在k层之上的祖先是p,问与u在同一层深度,并且公共祖先都是p的节点有多少个
题目分析:因为先要求出u在第k层之上的祖先,我们可以利用树上倍增很简单的求出,但接下来的操作我是没想到的,虽然鲲学长已经给了提示需要二分,我也看出了直接dfs暴力找肯定会T掉的,但没设计出合适的二分来处理这个题,憋不住去网上看了题解后一下子就豁然开朗了,大概就是先用dfs序预处理一下,然后每一层开一个vector,用来储存每个节点在dfs序后的编号,因为dfs序的缘故,我们在求出u在第k层之上的祖先p后,就可以知道p的子树中的所有编号肯定都在[L[p],R[p]]之间了,那么我们只需要对于deep[u]这一层的向量进行二分,求出有多少个节点位于这个范围内就是答案了
实现简单但思路不好想,挺好的一个题目
2020. 1.14更新:
学了一波树上启发式合并,发现对于这个题目而言,要求以每个结点为根的子树的状态,不妨直接用启发式合并搞一波,因为给出的数据是针对于子节点和相对深度,我们可以利用树上倍增预处理出祖先节点和绝对深度,如此一来就是树上启发式合并的模板题了
代码:
二分:
#include<iostream>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<climits>
#include<cmath>
#include<cctype>
#include<stack>
#include<queue>
#include<list>
#include<vector>
#include<set>
#include<map>
#include<sstream>
using namespace std;
typedef long long LL;
const int inf=0x3f3f3f3f;
const int N=1e5+100;
vector<int>node[N];
vector<int>d[N],root;
int n,limit,max_deep;
int cnt=0;
int L[N],R[N];
int dp[N][20],deep[N];
void dfs(int u,int fa,int dep)//dfs序+树上倍增
{
max_deep=max(max_deep,dep);
L[u]=++cnt;
d[dep].push_back(cnt);
deep[u]=dep;
dp[u][0]=fa;
for(int i=1;i<=limit;i++)
dp[u][i]=dp[dp[u][i-1]][i-1];
for(int i=0;i<node[u].size();i++)
{
int v=node[u][i];
if(v==fa)
continue;
dfs(v,u,dep+1);
}
R[u]=cnt;
}
int solve(int u,int p)
{
int h=deep[u];
for(int i=0;i<=limit;i++)//先跳到祖先p的位置
if((p>>i)&1)
u=dp[u][i];
if(u==0)//若跳到0节点了,说明祖先不存在,直接返回0
return 0;
int a=L[u],b=R[u];//查找deep[u]层在[a,b]区间内有多少个节点
int l=lower_bound(d[h].begin(),d[h].end(),a)-d[h].begin();
int r=upper_bound(d[h].begin(),d[h].end(),b)-d[h].begin()-1;
return r-l;
}
int main()
{
// freopen("input.txt","r",stdin);
// ios::sync_with_stdio(false);
scanf("%d",&n);
limit=log2(n)+1;
for(int i=1;i<=n;i++)
{
int num;
scanf("%d",&num);
if(num)
node[num].push_back(i);
else
root.push_back(i);
}
for(int i=0;i<root.size();i++)
dfs(root[i],0,0);
for(int i=0;i<=max_deep;i++)
sort(d[i].begin(),d[i].end());
int w;
scanf("%d",&w);
while(w--)
{
int a,b;
scanf("%d%d",&a,&b);
printf("%d ",solve(a,b));
}
return 0;
}
树上启发式合并:
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cstring>
#include<algorithm>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<unordered_map>
using namespace std;
typedef long long LL;
const int inf=0x3f3f3f3f;
const int N=1e5+100;
int deep[N],son[N],num[N],dp[N][20],ans[N],cnt[N],limit;
bool vis[N];
vector<int>node[N];
vector<pair<int,int>>Q[N];//<id,deep>
void dfs_son(int u,int dep)
{
son[u]=-1;
num[u]=1;
deep[u]=dep;
for(int i=1;i<=limit;i++)
dp[u][i]=dp[dp[u][i-1]][i-1];
for(auto v:node[u])
{
dfs_son(v,dep+1);
num[u]+=num[v];
if(son[u]==-1||num[son[u]]<num[v])
son[u]=v;
}
}
void cal(int u,int val)
{
cnt[deep[u]]+=val;
for(auto v:node[u])
{
if(vis[v])
continue;
cal(v,val);
}
}
void dfs(int u,int keep)
{
for(auto v:node[u])
{
if(v==son[u])
continue;
dfs(v,0);
}
if(son[u]!=-1)
{
dfs(son[u],1);
vis[son[u]]=true;
}
cal(u,1);
for(auto i:Q[u])
{
int id=i.first;
int d=i.second;
ans[id]=cnt[d];
}
if(son[u]!=-1)
vis[son[u]]=false;
if(!keep)
cal(u,-1);
}
int get_Ancestor(int x,int d)
{
for(int i=0;i<=limit;i++)
if((d>>i)&1)
x=dp[x][i];
return x;
}
int main()
{
// freopen("input.txt","r",stdin);
// ios::sync_with_stdio(false);
int n,m;
scanf("%d",&n);
limit=log2(n)+1;
for(int i=1;i<=n;i++)
{
int fa;
scanf("%d",&fa);
node[fa].push_back(i);
dp[i][0]=fa;
}
dfs_son(0,0);
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
int u,d;
scanf("%d%d",&u,&d);
if(deep[u]<=d)
ans[i]=1;
else
{
int fa=get_Ancestor(u,d);
Q[fa].push_back(make_pair(i,deep[u]));
}
}
dfs(0,1);
for(int i=1;i<=m;i++)
printf("%d ",ans[i]-1);
return 0;
}