2021牛客OI赛前集训营-树数树【树上启发式合并,堆】
正题
题目链接:https://ac.nowcoder.com/acm/contest/20107/C
题目大意
给出\(n\)个点的一棵树,求一个最长的序列使得数字互不相同且相邻编号节点的都是祖孙关系。
\(1\leq n\leq 10^5,1\leq T\leq 5\)
解题思路
\(w_{x,i}\)表示节点\(x\)第\(i\)次下来最多能获得的代价(保证前面最优的情况),那么转移的时候我们之间把子节点的\(w\)从大到小排序然后因为节点\(x\)有一次额外机会向下,所以我们还要把前两个最大的\(w\)给合并。
这个过程用启发式合并+堆就好了,时间复杂度:\(O(n\log n)\)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N=1e5+10;
struct node{
int to,next;
}a[N<<1];
int T,n,tot,ls[N],siz[N],son[N];
priority_queue<int> s[N],q;
void addl(int x,int y){
a[++tot].to=y;
a[tot].next=ls[x];
ls[x]=tot;return;
}
void dfs(int x,int fa){
siz[x]=1;
for(int i=ls[x];i;i=a[i].next){
int y=a[i].to;
if(y==fa)continue;
dfs(y,x);siz[x]+=siz[y];
if(siz[y]>siz[son[x]])son[x]=y;
}
return;
}
void solve(int x,int fa,int top){
for(int i=ls[x];i;i=a[i].next){
int y=a[i].to;
if(y==fa||y==son[x])continue;
solve(y,x,y);
}
if(son[x])solve(son[x],x,top);
for(int i=ls[x];i;i=a[i].next){
int y=a[i].to;
if(y==fa||y==son[x])continue;
while(!s[y].empty())q.push(s[y].top()),s[y].pop();
}
int w=0;
if(!q.empty())w+=q.top(),q.pop();
if(!q.empty())w+=q.top(),q.pop();
q.push(w+1);
if(x==top)
while(!q.empty())s[x].push(q.top()),q.pop();
return;
}
int main()
{
scanf("%d",&T);
while(T--){
memset(ls,0,sizeof(ls));
memset(son,0,sizeof(son));
scanf("%d",&n);tot=0;
for(int i=1;i<n;i++){
int x,y;
scanf("%d%d",&x,&y);
addl(x,y);addl(y,x);
}
dfs(1,1);solve(1,1,1);
printf("%d\n",s[1].top());
while(!s[1].empty())s[1].pop();
}
return 0;
}