HDU - 5788 Level Up(主席树+dfs序+树状数组)

题目链接:点击查看

题目大意:给出一棵有向树,每个节点都有一个初始的权值 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;
}

猜你喜欢

转载自blog.csdn.net/qq_45458915/article/details/106847128