*题解:P7897 [Ynoi2006] spxmcq

题目链接

解析

考虑暴力怎么做,发现是一个简单的树形 DP,设 \(f_i\) 表示以 \(i\) 为根的子树内,包含点 \(i\) 的合法点集的最大权值和。那么有:

\[f_i = a_i + x + \sum \max(f_j,0) \]

其中 \(j\) 为 \(i\) 的儿子。

这个 \(\max\) 很烦,它的含义可以理解为是对于 \(i\) 只考虑 \(f_j \ge 0\) 的儿子 \(j\)。而 \(f_j\) 随着 \(x\) 的递增而递增,将每个 \(x\) 看作是一个时刻,在 \(x\) 的递增过程中,每个 \(j\) 至多有一个时刻由不考虑变为考虑。故可以在该时刻将其信息上传至父亲并更新 \(f\),一直上传直到上传到第一个“未被考虑的结点”为止,该结点的被考虑时刻将会提前。

一个父亲在某一时刻变得被考虑可以视作是连边,类似初始转移方程,\(f_i = a_i + x + \sum f_j\),其中 \(j\) 是 \(i\) 的儿子且在 \(i\) 所在连通块内。记 \(siz_i\) 表示以 \(i\) 为根的子树内,在 \(i\) 所在连通块内的结点数量。那么式子就可以改写为 \(f_i = g_i + siz_i\cdot x\),其中 \(g_i\) 表示在 \(i\) 子树内且在同一连通块内的结点的 \(a\) 之和。

故每次由 \(j\) 开始的上传可以看作是链上的两个操作:\(siz_i\leftarrow siz_i + siz_j\) 和 \(g_i \leftarrow g_i + g_j\)。另有一个操作是查询时的单点查 \(f\)。链加单点查,直接树剖是两只 \(\log\),不妨差分一下变为单点加子树查,这样就可以利用 dfs 序配合树状数组做到单 \(\log\)。

我们还需要每次取出最小时刻,更新指定时刻 \(x_i\),可以使用懒惰删除的 priority_queue 来维护。更新时,要求满足 \(f_i = g_i+ siz_i\cdot x_i \ge 0\) 的最小 \(x_i\),即求满足 \(x_i\ge\dfrac{-g_i}{siz_i}\) 的最小 \(x_i\)。避免精度误差我们令 \(x\) 为整数,那么 \(\left\lceil \dfrac{-g_i}{siz_i} \right \rceil\) 即为所求。

时间复杂度 \(O(n\log n)\)。

总结

根据单调性,寻找函数值突变分界点。

代码

注意负数除法上取整的写法。

#include <bits/stdc++.h>
#define mid ((l + r) >> 1)
#define ls(x) ((x) << 1)
#define rs(x) (((x) << 1) | 1)
using namespace std;
typedef long long ll;
typedef pair<ll,int> pli;
const int N = 1e6 + 5,M = 20,mod = 571373,mod2 = 1e9 + 7,base1 = 121,base2 = 13331;
int dfn[N],fat[N],sz[N],cnt;
int fa[N],tp[N];
int a[N];
ll siz[N],g[N];
bool vis[N];
vector<int> t[N];
ll val[N];
void dfs(int x){
	dfn[x] = ++cnt;	
	sz[x] = 1;
	for(int nx : t[x])if(nx != fat[x]){
		dfs(nx);
		sz[x] += sz[nx];
	}
}
int find(int x){
	if(x != fa[x]) fa[x] = find(fa[x]);
	return fa[x];
}
void merge(int a,int b){
	int p = find(a),q = find(b);
	fa[p] = q;
}
struct Q{
	int u,x,id;
}q[N];
bool cmp(Q a,Q b){
	return a.x < b.x;
}
ll res[N];
void add(ll *b,int x,ll k){
	x++;
	for(;x < N;x += x & -x){
		b[x] += k;
	}
}
ll ask(ll *b,int x){
	x++;
	ll res = 0;
	for(;x;x -= x & -x){
		res += b[x];
	}
	return res;
}
ll get(ll *b,int pos){
	return ask(b,dfn[pos] + sz[pos] - 1) - ask(b,dfn[pos] - 1);
}
int read(){
	int a = 1,x = 0;
	char ch = getchar();
	while(ch > '9' || ch < '0'){
		if(ch == '-') a = -1;
		ch = getchar();
	}
	while(ch >= '0' && ch <= '9'){
		x = x * 10 + ch - '0';
		ch = getchar();
	}
	return a * x;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n,m;
	cin>>n>>m;	
	for(int i=2;i<=n;i++){
		cin>>fat[i];
		t[i].push_back(fat[i]);
		t[fat[i]].push_back(i);
	}
	dfs(1);
	for(int i=1;i<=n;i++){
		fa[i] = i;
		tp[i] = fat[i];
	}
	priority_queue<pli,vector<pli>,greater<pli> > pq;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		add(g,dfn[i],a[i]),add(g,dfn[fat[i]],-a[i]);
		add(siz,dfn[i],1),add(siz,dfn[fat[i]],-1);
		pq.push({-a[i],i});
		val[i] = -a[i];
	}
	for(int i=1;i<=m;i++){
		int u,x;
		cin>>u>>x;
		q[i] = {u,x,i};
	}
	sort(q + 1,q + m + 1,cmp);
	int now = 1;
	while(!pq.empty()){
		pli p = pq.top();
		pq.pop();
		while(now <= m && q[now].x < p.first){
			int u = q[now].u,du = dfn[q[now].u];
			ll gu = get(g,u),sizu = get(siz,u);
			res[q[now].id] = gu + sizu * q[now].x;
			now++;
		}
		int u = p.second,du = dfn[u];
		int pos = find(fat[find(u)]),dp = dfn[pos];
		if(vis[u]) continue;
		vis[u] = true;
		int dfa = dfn[fat[u]];
		ll gu = get(g,u),sizu = get(siz,u);
		int fap = fat[pos],dfap = dfn[fap];
		add(g,dfa,gu),add(siz,dfa,sizu);
		if(!pos) continue;
		add(g,dfap,-gu),add(siz,dfap,-sizu);		
		ll gf = get(g,pos),sizf = get(siz,pos);
		ll nx = (-gf > 0 ? (-gf + sizf - 1) / sizf : -gf / sizf);
		merge(u,pos);
		val[pos] = nx;
		pq.push({nx,pos});
	}
	while(now <= m){
		int u = q[now].u,du = dfn[q[now].u];
		ll gu = get(g,u),sizu = get(siz,u);
		res[q[now].id] = gu + sizu * q[now].x;
		now++;
	}
	for(int i=1;i<=m;i++){
		cout<<res[i]<<"\n";
	}
	return 0;
}
posted @ 2026-11-06 21:06  yutar  阅读(3)  评论(0)    收藏  举报