loj3166. 「CEOI2019」魔法树

题面

题意:自已去看

题解:首先考虑dp。设(dp_{i,j})表示(i)的子树内,总时间为(j)时的最大收益。转移是显然的,能对其造成贡献的是每一个儿子(v)(dp_{v,k}(k leq j))。然后再加上自己的贡献即可。

优化1:发现有用的时间只有(n)种,所以可以将时间离散化。时间复杂度是(O(n^2))的。

优化2:我们先不考虑当前节点的贡献。发现两个儿子直接合并的复杂度是(O(n))的,考虑优化。发现对于每个(i)和其子树内出现过的(j)(dp_{i,j})一定是递增的,因为(dp_{i,j}=max(dp_{i,j},dp_{i,j-1}))。所以如果我们用map记录(dp)数组,并将(dp)数组差分,那么就能做到启发式合并了。直接合并两个map即可。

最后考虑当前节点的果实的贡献。设当前节点果实成熟的时间为(t),那么(dp_{i,t}+=w_i),然后考虑有一些(dp_{i,j}(j>t))会比(dp_i,t)小,那么找到这些小的,把它们从map里删除就行了。

时间复杂度:(O(nlog^2n))

代码:

#include<bits/stdc++.h>
using namespace std;
#define re register int
#define F(x,y,z) for(re x=y;x<=z;x++)
#define FOR(x,y,z) for(re x=y;x>=z;x--)
typedef long long ll;
#define I inline void
#define IN inline int
#define C(x,y) memset(x,y,sizeof(x))
#define STS system("pause")
template<class D>I read(D &res){
	res=0;register D g=1;register char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')g=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		res=(res<<3)+(res<<1)+(ch^48);
		ch=getchar();
	}
	res*=g;
}
vector<int>e[101000],vec[101000];
map<int,ll>s[101000];
int n,m,k,fa[101000],root[101000],t[101000];
ll w[101000];
ll ans,sum;
inline bool bbb(int x,int y){return t[x]==t[y]?w[x]<w[y]:t[x]<t[y];}
I D_1(int x){
	for(auto d:e[x]){
		D_1(d);
		if(s[x].size()<s[d].size())swap(s[x],s[d]);
		for(auto it=s[d].begin();it!=s[d].end();it++){
			s[x][it->first]+=it->second;
		}
		s[d].clear();
	}
	sort(vec[x].begin(),vec[x].end(),bbb);
	for(auto d:vec[x]){
		s[x][t[d]]+=w[d];register ll dt=w[d];
		for(auto it=s[x].upper_bound(t[d]),tmp=it;it!=s[x].end();){
			if(dt>=it->second){dt-=it->second;tmp=it++,s[x].erase(tmp);}
			else{it->second-=dt;break;}
		}
	}
}
int main(){
	read(n);read(m);read(k);
	F(i,2,n)read(fa[i]),e[fa[i]].emplace_back(i);
	re X;
	F(i,1,m){
		read(X);vec[X].emplace_back(i);
		read(t[i]);read(w[i]);
	}
	D_1(1);
	for(auto it=s[1].begin();it!=s[1].end();it++){
		sum+=it->second;
	}
	printf("%lld",sum);
	return 0;
}
/*
6 4 10
1
2
1
4
4
3 4 5
4 7 2
5 4 1
6 9 3
*/

原文地址:https://www.cnblogs.com/Purple-wzy/p/13308283.html