【NOI模拟】Forest【set】

传送门题目可以qq找我要

每个点只有一个出点。维护权值c。

同样我们从修改对答案的影响这个角度来思考问题。

如果我修改了一个点的出边,就会修改度数,修改度数就会修改E。而修改E会修改这个点周围一圈的所有C。

这号rilong啊,,复杂度爆炸。

抓住出边为1这个条件。再将被影响的点分类。

父亲:单点修改,我:单点修改,儿子们:一大群,我们可以找个什么东西来维护。

我们发现权值的计算公式是加法,所以我们大可以先将所有儿子的c中减去我的E。计算答案的时候加回来就是。

我们成功让我的E修改与儿子无关啦。

问题说是求最大最小。能维护最大最小最方便的数据结构(最好是STL)是啥呢,,当然是set啦。

multiset嘛。

确定了数据结构,我们再从头思考一下每次修改的影响。

我和父亲断边,影响了父亲的D,E,C。因为影响了父亲的E,所以影响了祖父的C。

而在set上,我影响了父亲,祖父和曾祖父。这三个人set都会发生变化。

顺带维护两个set,一个最大一个最小。最大就是所有点set的最大。最小就是所有set的最小。

所以在修改的时候也要把答案先删除。最后插入。

然后就没了,,疯狂set就是了。

//a:指向谁。
//b:糖果数
//c:不包含父亲的权值。 
//d:度数+自己 
//e:b/d 
//s:维护儿子的权值(注意儿子权值没有包含自己)。 
#include<bits/stdc++.h>
using namespace std;
#define in read()
#define int long long
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;
		ch=getchar();
	}return cnt*f;
}
int n,m;
const int N=1e5+3;
int a[N],b[N],c[N],d[N],e[N];
multiset<int> q[N],minn,maxx; 
multiset<int>::iterator it; //留作纪念的指针qwq 
//x原来指向z,现在指向y。
//z的度数-1,e改变,c改变,set去掉该子节点。
//x度数不变,e不变,c改变,指向改变。
//y度数+1,e改变,c改变,set加上x。 
//minn,maxx改变,z,y先删再加。 
//
//void modify(int x,int y){
//	if(a[x]==y)return;
//	c[a[x]]+=d[a[x]]*e[a[x]];
//	d[x]--;d[a[x]]--;int z=a[x];
//	c[z]-=e[x];c[z]=-=e[z];e[z]=b[z]/d[z];c[z]+=e[z];
//	c[z]-=d[z]*e[z];q[z].erase((q[z].lower_bound(c[x]))); 
//} 
//尝试拆分问题 
//删除最大最小贡献。修改一个点会影响其父亲的dec,祖父的c。所以会影响其父,祖父,曾祖父 三人的set。全部删除。 
inline void modify(int to,int E,int C,int inv){
	if(!q[to].empty()){
		minn.erase(minn.lower_bound((*q[to].begin())+e[to]));
		maxx.erase(maxx.lower_bound((*--q[to].end())+e[to]));
	}
	
	if(inv==-1)q[to].erase(q[to].lower_bound(C));							//删去此儿子。 
	
	//此处注意顺序,先删除对答案贡献,再剔除内部信息。
	minn.erase(minn.lower_bound((*q[a[to]].begin())+e[a[to]]));				//祖父 
	minn.erase(minn.lower_bound((*q[a[a[to]]].begin())+e[a[a[to]]]));		//祖父 
	maxx.erase(maxx.lower_bound((*--q[a[to]].end())+e[a[to]]));				//曾祖父 
	maxx.erase(maxx.lower_bound((*--q[a[a[to]]].end())+e[a[a[to]]]));		//曾祖父 
	q[a[to]].erase(q[a[to]].lower_bound(c[to]));							//祖父 
	q[a[a[to]]].erase(q[a[a[to]]].lower_bound(c[a[to]]));					//曾祖父 
	
	//按照从上到下的顺序消除贡献,再从下到上增加贡献。 
	c[a[to]]-=e[to];c[to]-=b[to]-d[to]*e[to]+e[to];
	d[to]+=inv;e[to]=b[to]/d[to];c[to]+=b[to]-d[to]*e[to]+e[to];c[to]+=inv*E;c[a[to]]+=e[to];
	
	//更新minn,maxx。注意顺序,先加入内部信息,再贡献答案
	q[a[to]].insert(c[to]);q[a[a[to]]].insert(c[a[to]]);
	minn.insert((*q[a[to]].begin())+e[a[to]]);
	minn.insert((*q[a[a[to]]].begin())+e[a[a[to]]]);
	maxx.insert((*--q[a[to]].end())+e[a[to]]);
	maxx.insert((*--q[a[a[to]]].end())+e[a[a[to]]]);
	
	if(inv==1)q[to].insert(C);												//加上此儿子
	
	if(!q[to].empty()){
		minn.insert((*q[to].begin())+e[to]);
		maxx.insert((*--q[to].end())+e[to]);
	}
} 
signed main(){
	//freopen("forest.in","r,",stdin);
	//freopen("forest.out","w",stdout);
	n=in;m=in;//cout<<"@#@# "<<m<<endl;
	for(register int i=1;i<=n;i++)b[i]=in,d[i]++;
	for(register int i=1;i<=n;i++)a[i]=in,d[a[i]]++,d[i]++;
	for(register int i=1;i<=n;i++)e[i]=b[i]/d[i],c[a[i]]+=e[i];
	for(register int i=1;i<=n;i++)c[i]+=b[i]-d[i]*e[i]+e[i];
	for(register int i=1;i<=n;i++)q[a[i]].insert(c[i]);
	for(register int i=1;i<=n;i++){//注意,c没包含父亲,所以当前set没装自己的e。 
		if(!q[i].empty())minn.insert((*q[i].begin())+e[i]),maxx.insert((*--q[i].end())+e[i]);//xxx
	} int op,x,y;
	while(m){--m;
		op=in;
		if(op==3){
			cout<<(*minn.begin())<<" "<<(*--maxx.end())<<'\n';
		}
		else if(op==2){
			x=in;cout<<c[x]+e[a[x]]<<'\n'; 
		}else if(op==1){
			x=in;y=in;
			if(a[x]==y)continue;
			modify(a[x],e[x],c[x],-1);
			a[x]=y;
			modify(a[x],e[x],c[x],1);
		}
	}
	return 0;
} 
/*
5 12
10 20 30 40 50
2 3 4 5 2
2 1
2 2
2 3
2 4
2 5
1 4 2
2 1
2 2
2 3
2 4
2 5
3
*/

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值