题目链接:点击查看
题目大意:给出一棵有向树,每个节点都有一个初始的权值 a[ i ] ,和一个通过计算得到的权值 mid[ i ] ,mid 数组的计算方法如下:mid[ u ] 为结点 u 及其子树中所有 a[ i ] 的中位数,现在问如果可以令其中一个节点的 a[ i ] 变为 1e5,如何选择可以使得所有节点的 mid[ i ] 之和最大
题目分析:乍一看题目可能比较复杂,但是一步一步分析下来可能就能找到突破口了
首先是子树,对于子树上进行操作,我们可以跑出整棵树的dfs序,这样就将子树上的操作转换为区间上的操作了
其次是中位数,经过上面dfs序的转换,我们现在的目标转换为了如何求一段区间上的中位数,换句话说,设 sz[ i ] 为点 i 的子树大小,现在需要求一段区间上第 ( sz[ i ] + 1 ) / 2 大的数是什么,区间上第 k 大的数,这里可以用主席树解决
再分析一下题意,将一个节点的权值 a 变为 1e5 ,1e5 在这个题目中相当于无穷大,如果节点 i 的 a[ i ] 变为 1e5 后,根据题意可知,其只会对特定的祖先产生影响,这些特定的祖先 x 必须满足 mid[ x ] >= a[ i ] ,这样当 a[ i ] 变为 1e5 后,祖先 x 的中位数就会变大为原本子树中第 ( sz[ i ] + 1 ) / 2 + 1 大的数了
这样我们可以通过dfs序和主席树,将 cur_mid[ i ] 和 next_mid[ i ] 预处理出来,其意义分别为结点 i 的中位数和结点 i 的中位数的下一个数
接下来就可以暴力枚举每个顶点,然后计算当前节点的权值 a 变为 1e5 后的影响了,不难发现,如果某一个权值 a[ i ] 变为 1e5 后,总的答案只会变大,不会变小,那么我们不妨设 sum 为所有 cur_mid[ i ] 之和,mmax 为变化后可以增加的答案,那么最终答案就是 sum + mmax 了
关于如何暴力枚举,我们可以借助 dfs 和树状数组来实现,树状数组中的每个点,其下标的意义是:祖先的中位数大小,维护的权值的意义是:如果更改子树中的结点 a[ v ] 为 1e5 后,当前节点可以提供的贡献
这样在枚举点 i 时,将其变化的值插入到树状数组中,也就是 next_mid[ i ] - cur_mid[ i ] 插入到位置 cur_mid[ i ] 中,同时查询点 i 的权值 a 更改为 1e5 后祖先节点的贡献,也就是 query( 100000 ) - query( a[ i ] - 1 ) 了,其意义就是查询祖先的中位数位于 [ a[ i ] , inf ] 中的贡献
代码:
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int N=1e5+100;
vector<int>node[N];//邻接表
int tot,L[N],R[N],sz[N];//树链剖分用
int cur_mid[N],next_mid[N];//题目用
LL sum,mmax;
/*树状数组*/
LL c[N];
int lowbit(int x)
{
return x&(-x);
}
void update(int pos,int val)
{
while(pos<N)
{
c[pos]+=val;
pos+=lowbit(pos);
}
}
LL query(int pos)
{
LL ans=0;
while(pos)
{
ans+=c[pos];
pos-=lowbit(pos);
}
return ans;
}
/*树状数组*/
/*主席树*/
struct Node
{
int l,r;
int sum;
}tree[N*20];
int cnt,root[N],a[N];
void update(int num,int &k,int l,int r)
{
tree[cnt++]=tree[k];
k=cnt-1;
tree[k].sum++;
if(l==r)
return;
int mid=l+r>>1;
if(num<=mid)
update(num,tree[k].l,l,mid);
else
update(num,tree[k].r,mid+1,r);
}
int query(int i,int j,int k,int l,int r)//i:左端点根节点编号,j:右端点根节点编号,k:第k大的数,l,r:区间[l,r]
{
int d=tree[tree[j].l].sum-tree[tree[i].l].sum;
if(l==r)
return l;
int mid=l+r>>1;
if(k<=d)
return query(tree[i].l,tree[j].l,k,l,mid);
else
return query(tree[i].r,tree[j].r,k-d,mid+1,r);
}
/*主席树*/
void dfs(int u)
{
sz[u]=1;
L[u]=++tot;
root[tot]=root[tot-1];
update(a[u],root[tot],1,N);
for(auto v:node[u])
{
dfs(v);
sz[u]+=sz[v];
}
R[u]=tot;
if(sz[u]==1)
{
cur_mid[u]=a[u];
next_mid[u]=100000;
sum+=cur_mid[u];
}
else
{
int mid=(sz[u]+1)>>1;
cur_mid[u]=query(root[L[u]-1],root[R[u]],mid,1,N);
next_mid[u]=query(root[L[u]-1],root[R[u]],mid+1,1,N);
sum+=cur_mid[u];
}
}
void DFS(int u)
{
update(cur_mid[u],next_mid[u]-cur_mid[u]);
mmax=max(mmax,query(100000)-query(a[u]-1));
for(auto v:node[u])
DFS(v);
update(cur_mid[u],cur_mid[u]-next_mid[u]);
}
void init()
{
for(int i=0;i<N;i++)
node[i].clear();
root[0]=0;
tree[0].l=tree[0].r=tree[0].sum=0;
cnt=1;
sum=tot=mmax=0;
}
int main()
{
#ifndef ONLINE_JUDGE
// freopen("input.txt","r",stdin);
// freopen("output.txt","w",stdout);
#endif
// ios::sync_with_stdio(false);
int n;
while(scanf("%d",&n)!=EOF)
{
init();
for(int i=1;i<=n;i++)
scanf("%d",a+i);
for(int i=2;i<=n;i++)
{
int x;
scanf("%d",&x);
node[x].push_back(i);
}
dfs(1);
DFS(1);
printf("%lld\n",mmax+sum);
}
return 0;
}