UOJ#418. 【集训队作业2018】三角形(线段树合并)

传送门

题解:
加入一个数,相当于是先加上 A i = w i A_i=w_i Ai=wi,再减去 B i = ∑ j ∈ s o n i w j B_i = \sum_{j \in son_i} w_j Bi=jsoniwj,然后代价就是一个操作序列的前缀最大值。

先考虑一下没有限制的的时候,怎么使得这个前缀最大值最小,我们可以分为两个部分: A i − B i < 0 , A i − B i ≥ 0 A_i - B_i \lt 0,A_i -B_i \ge 0 AiBi<0,AiBi0。显然 A i − B i < 0 A_i - B_i \lt0 AiBi<0的要放在 A i − B i ≥ 0 A_i -B_i \ge 0 AiBi0的前面。 先考虑 A i − B i < 0 A_i -B_i \lt 0 AiBi<0

此时我们假设一个二分值 T T T,那么每次就是把 A i A_i Ai小等于某个值里面最小的那个拿出来,然后继续这个过程,于是我们只需要按照 A i A_i Ai从小到大排序即可。 A i − B i ≥ 0 A_i - B_i \ge 0 AiBi0的相当于使得后缀最小值最大,就是反过来的前缀最大值最小,其实就是按照 B i B_i Bi从大到小排序。

然后考虑有限制怎么办,我们也把这个过程反过来,看作是从根开始选,每次加上 ∑ j ∈ s o n i w j \sum_{j \in son_i}w_j jsoniwj再减去 w i w_i wi。 这个时候我们会发现最小的那个值一定在父亲选了之后马上选,我们可以用个堆维护一下,每次把最小的和父亲合并起来,这样就可以得到选择的顺序了(每个子树的顺序是其子序列)。

然后知道了序列长啥样,剩下的就是一个线段树合并了,时间复杂度 O ( n log ⁡ n ) O(n \log n) O(nlogn)

#include 
using namespace std;
typedef long long LL;

const int RLEN=1<<18|1;
inline char nc() {
	static char ibuf[RLEN],*ib,*ob;
	(ib==ob) && (ob=(ib=ibuf)+fread(ibuf,1,RLEN,stdin));
	return (ib==ob) ? -1 : *ib++;
}
inline int rd() {
	char ch=nc(); int i=0,f=1;
	while(!isdigit(ch)) {if(ch=='-')f=-1; ch=nc();}
	while(isdigit(ch)) {i=(i<<1)+(i<<3)+ch-'0'; ch=nc();}
	return i*f;
}
inline void W(LL x) {
	static int buf[50];
	if(!x) {putchar('0'); return;}
	if(x<0) {putchar('-'); x=-x;}
	while(x) {buf[++buf[0]]=x%10; x/=10;}
	while(buf[0]) putchar(buf[buf[0]--]+'0');
}

const int N=2e5+50;
int n,w[N],p[N],fa[N],anc[N],tot; 
LL s[N],w2[N],ans[N];
int rt[N],lc[N*30],rc[N*30];
LL sw[N*30],mx[N*30];
vector <int> son[N];

inline void upt(int x) {
	sw[x]=sw[lc[x]]+sw[rc[x]];
	mx[x]=max(mx[lc[x]],sw[lc[x]]+mx[rc[x]]);
}
inline void inc(int &k,int l,int r,int p,int w,LL v) {
	if(!k) k=++tot;
	if(l==r) {sw[k]=w-v; mx[k]=w; return;}
	int mid=(l+r)>>1;
	(p<=mid) ? inc(lc[k],l,mid,p,w,v) : inc(rc[k],mid+1,r,p,w,v);
	upt(k);
}
inline int merge(int x,int y) {
	if(!x) return y;
	if(!y) return x;
	lc[x]=merge(lc[x],lc[y]);
	rc[x]=merge(rc[x],rc[y]);
	upt(x); return x;
}
inline void dfs(int x) {
	for(auto v:son[x])
		dfs(v), rt[x]=merge(rt[x],rt[v]);
	ans[x]=max(mx[rt[x]],sw[rt[x]]+w[x]);
	inc(rt[x],1,n,p[x],w[x],s[x]);
}
struct Que {
	deque <int> *q;
	inline void init(int i) {q=new deque <int>; q->push_back(i);}
	inline void merge(Que &b) {
		if(q->size()<b.q->size()) {
			swap(q,b.q);
			for(int i=b.q->size()-1;~i;i--) q->push_front(b.q->operator[](i));
		} else {
			for(int i=0;i<b.q->size();i++) q->push_back(b.q->operator[](i));
		} delete b.q;
	}
} q[N];
struct cmp {
	inline bool operator ()(int x,int y) {
		if(w2[x]<0 && w2[y]>=0) return true;
		if(w2[x]>=0 && w2[y]<0) return false;
		if(w2[x]<0) return (s[x]<s[y] || (s[x]==s[y]&&x<y));
		else return (w2[x]-s[x]>w2[y]-s[y] || (w2[x]-s[x]==w2[y]-s[y]&&x<y));
	}
};
set <int,cmp> S;
inline int ga(int x) {return (anc[x]==x) ? x : (anc[x]=ga(anc[x]));}
int main() {
	rd(); n=rd();
	for(int i=2;i<=n;i++) son[fa[i]=rd()].push_back(i);
	for(int i=1;i<=n;i++) w[i]=rd();
	for(int i=1;i<=n;i++) for(auto v:son[i]) s[i]+=w[v];
	for(int i=1;i<=n;i++) q[i].init(i), anc[i]=i, w2[i]=s[i]-w[i], S.insert(i);
	while(!S.empty()) {
		int u=*S.begin(); S.erase(u);
		if(!ga(fa[u])) {
			for(auto v:*q[u].q) p[v]=n-tot++;
			anc[u]=0;
		} else {
			anc[u]=fa[u];
			S.erase(ga(u));
			q[ga(u)].merge(q[u]);
			s[ga(u)]=max(s[ga(u)],w2[ga(u)]+s[u]);
			w2[ga(u)]=w2[ga(u)]+w2[u];
			S.insert(ga(u));
		}
	} 
	for(int i=1;i<=n;i++) {
		s[i]=0;
		for(auto v:son[i]) s[i]+=w[v];
	}
	dfs(1);
	for(int i=1;i<=n;i++) W(ans[i]), putchar(' ');
}

你可能感兴趣的:(线段树)