• P6118[JOI 2019 Final]珍しい都市【树的直径】


    正题

    题目链接:https://www.luogu.com.cn/problem/P6118


    题目大意

    给出一棵\(n\)个点的树,对于一个点\(x\)来说,独特的点\(y(y\neq x)\)是指不存在\(z\)使得\(dis(y,x)=dis(z,x)\),其中\(x\neq z,y\neq z\)

    每个点有个颜色,对于每个点\(x\)对于它来说独特的点所包含的颜色种类数。

    \(1\leq n\leq 2\times 10^5\)


    解题思路

    首先我们很容易得到一个结论就是记距离\(x\)最远的点是\(y\),那么对于点\(x\)独特的点肯定都在\(x\leftrightarrow y\)的路径上。(证明很简单,因为不在这个上面的肯定能在这条路径上找到一个点和它深度相等)

    然后树上有一个性质就是距离任意点\(x\)最远的点\(y\)肯定在树的直径的某一端。

    那么我们现在考虑对于一个点\(x\),快速求出所有其他点\(y\)路径\(x\rightarrow y\)上点的贡献。

    我们考虑用一个东西去存目前\(x\rightarrow y\)路径上的节点集合\(S\),然后对于每个点预处理出一个子树深度最深的儿子和次深的儿子。

    然后如果我们不是往最深那个儿子那里走,记它的深度为\(dep\),那么集合\(S\)中距离\(x\)超过\(dep+1\)的点都不会产生贡献。

    如果是往最深那个走,那么就是次深那个节点的深度。

    我们考虑直接用一个栈来维护这个集合\(S\),然后每次先往最深的那个儿子走再走其他的儿子,实时弹出集合\(S\)中的节点。

    但是这样我们回溯的时候是不是需要把之前弹出来的节点还回去呢,能够惊喜的发现,继续往回到分叉的时候,这些节点依旧要被弹出,因为刚刚走过的子树有这些深度的节点。

    所以这样每个点就最多被弹出一次了。

    时间复杂度:\(O(n)\)


    code

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<stack>
    using namespace std;
    const int N=2e5+10;
    struct node{
    	int to,next;
    }a[N<<1];
    int n,m,tot,sum,root,mx,ls[N],col[N],ans[N];
    int son[N],ton[N],len[N],c[N],dep[N],las[N];
    stack<int> s,tmp[N];
    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,int dep){
    	if(dep>mx){mx=dep;root=x;}
    	for(int i=ls[x];i;i=a[i].next){
    		int y=a[i].to;
    		if(y==fa)continue;
    		dfs(y,x,dep+1);
    	}
    	return;
    }
    void ins(int x)
    {sum+=(!c[col[x]]);c[col[x]]++;return;}
    void del(int x)
    {c[col[x]]--;sum-=(!c[col[x]]);return;}
    void calc(int x,int fa){
    	len[x]=son[x]=ton[x]=0;
    	for(int i=ls[x];i;i=a[i].next){
    		int y=a[i].to;
    		if(y==fa)continue;
    		dep[y]=dep[x]+1;calc(y,x);
    		len[x]=max(len[x],len[y]+1);
    		if(len[y]>len[son[x]])
    			ton[x]=son[x],son[x]=y;
    		else if(len[y]>len[ton[x]])
    			ton[x]=y;
    	}
    	return;
    }
    void sets(int x,int len){
    	while(!s.empty()&&dep[x]-dep[s.top()]<=len)
    		del(s.top()),s.pop();
    	return;
    }
    void solve(int x,int fa){
    	sets(x,len[ton[x]]+1);
    	s.push(x);ins(x);
    	if(son[x])solve(son[x],x);
    	if(!son[son[x]])s.pop(),del(x);
    	sets(x,len[son[x]]+1);
    	if(dep[x]>las[x])
    		ans[x]=sum,las[x]=dep[x];
    	for(int i=ls[x];i;i=a[i].next){
    		int y=a[i].to;
    		if(y==fa||y==son[x])continue;
    		s.push(x);ins(x);
    		solve(y,x);
    		if(!son[y])s.pop(),del(x);
    	}
    	return;
    }
    int main()
    {
    	scanf("%d%d",&n,&m);len[0]=-1;
    	for(int i=1,x,y;i<n;i++){
    		scanf("%d%d",&x,&y);
    		addl(x,y);addl(y,x);
    	}
    	for(int i=1;i<=n;i++)scanf("%d",&col[i]);
    	dfs(1,0,1);
    	calc(root,0);
    	solve(root,0);
    	mx=0;dfs(root,0,1);
    	dep[root]=0;calc(root,0);
    	solve(root,0);
    	for(int i=1;i<=n;i++)
    		printf("%d\n",ans[i]);
    	return 0;
    }
    
  • 相关阅读:
    想当老板的人,三点特征很重要(转)
    突破三个自我,你就不光是老板的料
    掌握这3套创业战略 保你赚到百万财富 
    也感山西黑窑洞
    再游府河有感
    朋友的影响力非常大,朋友决定你的财富
    夏日乘凉
    职业生涯的八大“定位法则”
    一生何求
    赠你一方明月
  • 原文地址:https://www.cnblogs.com/QuantAsk/p/16377976.html
Copyright © 2020-2023  润新知