题意:给你一颗树,每个点有权值,每个树的的价值是这颗树上点权不同的个数,现在你可以去掉一条边变成两棵树,问这两颗树的价值和最大是多少。
思路:去掉一条边相当于选取了整颗树的一个子树,子树所有节点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;
}