Split The Tree HDU - 6504

题意:给你一颗树,每个点有权值,每个树的的价值是这颗树上点权不同的个数,现在你可以去掉一条边变成两棵树,问这两颗树的价值和最大是多少。

思路:去掉一条边相当于选取了整颗树的一个子树,子树所有节点dfs序是连续的,就是求连续区间不同数的个数,剩下的部分可以通过将序列复制一遍相连。

求连续区间不同数的个数有多种方法,莫队算法,主席树,树状数组。

树状数组:对于一个数列,每个数字第一次出现的位置标记为1,然后以1为左端点的区间的种数就是1->R之间标记的个数,也就是前缀和,因此用树状数组维护。若区间左端点为2呢?我们一步一步走,刚才我们在1处,现在我们要到2处,故1处要丢掉,也就是把标记清为0,并且,把1处数字下一次出现的位置标记为1,好,现在就能直接询问2->R的区间种数了,即R的前缀和了。 该做法的巧妙之处在于只标记每个数字以L为起点时第一次出现的位置,故达到去重目的。

#include<bits/stdc++.h>

#define ll long long
const ll mod=1000000007;
using namespace std;
struct code
{
    int x,nx;
} edge[1000005];
int head[100005],top;
int dl[100005],dr[100005];
int times=0;
void add(int s,int e)
{
    edge[top].x=e;
    edge[top].nx=head[s];
    head[s]=top++;
}
int lowbit(int x)
{
    return x&(-x);
}
int tree[200005];
void up(int x,int d)
{
    while(x<200005)
    {
        tree[x]+=d;
        x+=lowbit(x);
    }
}
int queuy(int x)
{
    int ans=0;
    while(x>0)
    {
        ans+=tree[x];
        x-=lowbit(x);
    }
    return ans;
}
int n,val[200005];
void dfs(int x)
{
    dl[x]=++times;
    for(int i=head[x]; i!=-1; i=edge[i].nx)
    {
        int v=edge[i].x;
        dfs(v);
    }
    dr[x]=times;
}
queue<int>dap[200005];//记录每个数出现的位置
map<int,int>p[200005];//记录区间答案
vector<int>q[200005];//记录要求的区间
int main()
{
    int x;
    while(~scanf("%d",&n))
    {
        memset(head,-1,sizeof(head));
        for(int i=2; i<=n; i++)
        {
            scanf("%d",&x);
            add(x,i);
        }
        times=0;
        dfs(1);
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&x);
            val[dl[i]]=x;
            val[dl[i]+n]=x;
            q[dl[i]].push_back(dr[i]);
            q[dr[i]+1].push_back(dl[i]-1+n);
        }
        for(int i=1; i<=2*n; i++)
        {
            p[i].clear();
            dap[val[i]].push(i);
        }
        for(int i=1; i<=100005; i++)
        {
            if(!dap[i].empty())
            {
                int x=dap[i].front();
                up(x,1);
            }
        }
        int ans=0;
        for(int i=1; i<=2*n; i++)
        {
            int len=q[i].size();
            for(int j=0; j<len; j++)
            {
                if(q[i][j]<i)
                    continue;
                p[i][q[i][j]]=queuy(q[i][j])-queuy(i-1);
            }
            q[i].clear();
            int x=dap[val[i]].front();
            up(x,-1);
            dap[val[i]].pop();
            if(!dap[val[i]].empty())
            {
                x=dap[val[i]].front();
                up(x,1);
            }
        }
        for(int i=1; i<=n; i++)
            ans=max(ans,p[dl[i]][dr[i]]+p[dr[i]+1][dl[i]-1+n]);
        printf("%d\n",ans);
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值