一道人生哲理题(一步一步优化)

https://vjudge.net/contest/500589#problem/D

一开始想拓扑,用了一个multiset维护其子节点对自己的贡献,然后每次将他的set遍历一遍,然后就,再决定他用不用加一下。

因为贪心所以尽量加到r[x],因为贪心,把当前点加到l[x],即可,因为他对父亲的贡献不是有自己决定的,有父亲节点决定。

但是TLE,分析,扫multiset的过程其实没必要,这样就是恰好满足r[x],但是用范围就行,

还是:决定父节点的不在自己,而在于其父亲。

所以只用知道儿子贡献总和的上限就行了,若大于l[x],不用处理,若小于r[x],贪心来讲加满更优,父节点的上界更大。还得向上传,因为每次只分析两层的状态,将状态向上传。

 

超时代码

点击查看代码
#include<cstdio>
#include<algorithm>
#include<set>
#include<queue>
using namespace std;
const int N=2e5+10;
typedef long long ll;
int cnt[N],p[N],n;
ll a[N],l[N],r[N],ans;
queue<int> q;
struct cmp{
	bool operator()(const ll& a,const ll& b){return a>b;}
};
multiset<ll,cmp> bar[N];
//表示子节点自己积累的
void init(){
	ans=0;
	fill(cnt+1,cnt+1+n,0);
	fill(a+1,a+1+n,0);
	for(int i=1;i<=n;++i)if(!bar[i].empty())bar[i].clear();
}
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		init();
		for(int i=2;i<=n;++i)scanf("%d",&p[i]),cnt[p[i]]++;
		for(int i=1;i<=n;++i)scanf("%lld%lld",&l[i],&r[i]);
		for(int i=1;i<=n;++i)if(!cnt[i])q.push(i),bar[i].insert(r[i]),ans++;
		while(!q.empty()){
			int x=q.front();q.pop();
			int f=p[x];cnt[f]--;
			if(!cnt[f])q.push(f);
			//只要小于r[x]就可以向上传,才会更大。若等于或大于没必要传。
			//若子节点的都传完了,那么就需要自己加了,这时答案就需要修改
			if(a[x]<r[x]){
				while(a[x]<r[x]&&!bar[x].empty()){
					ll maxn=*bar[x].begin();bar[x].erase(bar[x].begin());
					ll Cha=r[x]-a[x];
					if(Cha<=maxn){
						a[x]=r[x];
						bar[f].insert(Cha);
					}else{
						a[x]+=maxn;
						bar[f].insert(maxn);
					}
				}
				if(a[x]<l[x]){
					ans++;
					bar[f].insert(r[x]-a[x]);
					a[x]=r[x];
				}		
			}
		}
		printf("%lld\n",ans);
	}
	return 0;
}

AC代码

点击查看代码
#include<cstdio>
#include<algorithm>
#include<set>
#include<queue>
using namespace std;
const int N=2e5+10;
typedef long long ll;
int cnt[N],p[N],n;
ll a[N],l[N],r[N],ans;
queue<int> q;
void init(){
	ans=0;
	fill(cnt+1,cnt+1+n,0);
	fill(a+1,a+1+n,0);
}
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		init();
		for(int i=2;i<=n;++i)scanf("%d",&p[i]),cnt[p[i]]++;
		for(int i=1;i<=n;++i)scanf("%lld%lld",&l[i],&r[i]);
		for(int i=1;i<=n;++i)if(!cnt[i])q.push(i);
		while(!q.empty()){
			int x=q.front();q.pop();
			int f=p[x];cnt[f]--;
			if(!cnt[f])q.push(f);
			if(a[x]<l[x])ans++,a[x]=r[x];
			if(a[x]>r[x])a[x]=r[x];
			a[f]+=a[x];
		}
		printf("%lld\n",ans);
	}
	//只要小于r[x]就可以向上传,才会更大。若等于或大于没必要传。
	//若子节点的都传完了,那么就需要自己加了,这时答案就需要修改
	//害。。。用什么multiset啊,用一个数组表示表示一下不就得了。
	return 0;
}
posted @ 2022-06-22 17:32  zasdcn  阅读(45)  评论(0编辑  收藏  举报