Distinctive Roots in a Tree CodeForces - 1467E (组合优化+dfs序 +差分)

题目链接

题意:给定一棵带点权无根树, 定义good root:good root到叶子节点任意一条路径上,都不包含相同元素。 问有多少个good root。

分析:参考大佬解法

我们先看相同的两个点
在这里插入图片描述
×的两个点权值相同,只会影响以它们为根的两棵树 。

我们需要把这两棵树所有节点都打上标记 ,所以我们可以跑一个dfs序 ,然后树的修改就变成区间修改了 ,方便用差分数组维护。
(跑dfs序需要根,我们可以令1为根。)

每对同权节点有两种情况:1、祖孙关系 2、非祖孙关系

  • 对于2:我们dfs完以u为根的树的时候,如果发现除了u树外还有a[u] ,那么把u树全部打上标记就ok了。

  • 对于1:参考下图
    在这里插入图片描述

A的子树S中存在节点B与A权值相同,那么我们应该要把B子树全部修改+1,同时S子树之外的所有节点也全部修改+1。大佬的实现方法非常巧妙,我们不管B子树的修改,因为B子树的修改会在第一种情况中出现,只需要把S子树外的节点修改即可。

综合一下,具体的做法就是:

1、dfs完以u为根的树的时候,如果a[u]的个数小于总的a[u]个数,那么把u树全部标记

2、在u的dfs过程中,发现u的v子树中还有节点点权等于a[u],那么就标记v子树以外的全部的点 。 
PS:标记v子树外全部的点,方法就是修改 [1,in[v]-1],[out[v]+1,n]这两个区间,
因为v子树dfs序区间为[in[v],out[v]]

最终差分数组统计前缀和,0代表没有被标记 ,满足good root。

#include<bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define ull unsigned long long
#define ll long long
#define pii pair<int, int>
const int maxn = 2e5 + 10;
const ll mod = 1e9 + 7;
const ll inf = (ll)4e16+5;
const int INF = 1e9 + 7;
const double pi = acos(-1.0);
ll inv(ll b){while(b==1)return 1;return(mod-mod/b)*inv(mod%b)%mod;}
inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){while(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int n;
int a[maxn];
vector<int> g[maxn];
int in[maxn],clk,sz[maxn];
map<int,int> mp,now;
int d[maxn];
void dfs1(int rt,int f)
{
    in[rt]=++clk;
    sz[rt]=1;
    now[a[rt]]++;//dfs到当前位置的点权记录
    int temp=now[a[rt]];
    int cnt=1;//当前子树中a[rt]的数量
    for(int i:g[rt])
    {
        if(i==f) continue;
        dfs1(i,rt);
        sz[rt]+=sz[i];
        if(now[a[rt]] > temp) //i子树中有a[rt] [in[i],out[i]]之外的区间都+1
        {
            d[1]++,d[in[i]]--;
            d[in[i]+sz[i]]++,d[n+1]--;
        }
        cnt+=(now[a[rt]]-temp);
        temp=now[a[rt]];
    }
    if(mp[a[rt]]>cnt) //rt子树之外还有a[rt]
    {
        //in[rt],out[rt]
        d[in[rt]]++;
        d[in[rt]+sz[rt]]--;
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",a+i),mp[a[i]]++;
    for(int i=1,u,v;i<n;i++)
    {
        scanf("%d %d",&u,&v);
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs1(1,0);
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        d[i]+=d[i-1];
        ans+=(!d[i]);
    }
    cout<<ans<<'\n';
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值