*题解:[AGC023F] 01 on Tree

题目链接

解析

首先可以发现,优先取点权为 \(0\) 的点必定不劣,\(0\) 靠左有助于减少逆序对个数。

考虑能取的点权值都为 \(1\) 的情况。若先取 \(i\) 后取 \(j\),则贡献为 \(f_i + 2f_j\),否则贡献为 \(f_j + 2f_i\),其中 \(f_i\) 表示取完 \(i\) 后最多能取多少个 \(0\) 权点。前者更优当且仅当 \(f_i + 2f_j < f_j + 2f_i\) 即 \(f_j < f_i\)。于是优先取 \(f\) 更大的那个点。

很遗憾这是错误的。为什么呢?因为取完 \(i\) 之后可能会出现新的可以取的 \(1\) 权点,有可能再取新出现的点是更优的,一样优的情况下又要比对下一层,时间复杂度不可接受。

那咋办?正难则反。考虑如果要取一个点必须先取后代怎么做。仿照正着做的过程,我们优先取点权为 \(1\) 的点,不同的是正着做是在树上删点,倒着做可以看作是初始都是孤立点然后不断加上连向父亲的边。然后就可以发现这样仍然没法做。

那咋办?先无视约束,假设我们从所有点中选了一个最优的点,那么我们希望该点尽量早被选取,此时考虑约束,就是在父亲被选后立即选取。选取完之后依旧向父亲连边,这样就会形成若干存有优先选取信息的连通块。于是选最优点变为选最优连通块,点向父亲连边就是两连通块连边,其意义为当父亲所在连通块被取后,该连通块应立即被取。

因为调换相邻两个连通块的顺序不会对其余连通块产生影响,考虑邻项交换。记两连通块分别为 \(i,j\),\(t_{x,y}\) 表示连通块 \(y\) 内 \(x\) 权点的个数。那么 \(i\) 比 \(j\) 先取更优,当且仅当 \(t_{1,i} \cdot t_{0,j} < t_{1,j} \cdot t_{0,i}\),即 \(\dfrac{t_{1,i}}{t_{0,i}} < \dfrac{t_{1,j}}{t_{0,j}}\)。那会不会出现不选使得 \(\dfrac{t_{1,x}}{t_{0,x}}\) 最小的 \(x\),选另一个连通块 \(y\) 再选拼起来的新连通块更优的情况呢?答案是不会。比值更大的连通块跟其父亲连通块拼接,新连通块的比值不小于原本二者中较小的比值,因此这样做并不优。

故每次取 \(\dfrac{t_{1,x}}{t_{0,x}}\) 最小的连通块然后连边,计算新连通块的比值即可,可以用并查集来维护连通块,用堆来维护最小比值。

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

想到了的地方

优先取 \(0\) 权点。

没想到的地方

正向取点的后效性。倒序处理。

先考虑全局最优,再考虑约束。

单点绑定成连通块。

为什么没想到/如何想到

想到一个假做法后思路回溯困难。

用相对顺序拼装得到绝对顺序。

代码

/*
考虑如何刻画祖先节点排在左侧,发现如果看成是一颗外向树那么一个合法的排列就对应着一个合法的拓扑序

那么如果当前入度为 0 的点中存在点权为 0 的,那么直接取必定不劣 

否则可选点权全 1,根据直觉我们希望后续的连续 0 个数最多,这个可以树形 dp 求解

考虑正确性,每个点权为 1 的点都可以对应着一个取了它之后最多可以连续取的 0 的个数
交换一下就发现连续 0 个数最多的放前面更优 

考虑所有的连续 0 个数相同的情况,取取完该点与连续的 0 权点后解锁出来的 1 权点中连续 0 个数最多的,还相等就再来。
最优选择可以表示为若干条链,链上每个点对应一个 1 权点,选连续 0 个数字典序最大的链,
考虑处理出链上走 2 ^ i 次到达的点和这段 hash 值,
然后找不相同的第一个位置判断。 

以上是错解。 
*/
#include <bits/stdc++.h>
#define ls(x) ((x) << 1)
#define rs(x) (((x) << 1) | 1)
using namespace std;
typedef long long ll;
typedef pair<ll,int> pii;
const int N = 2e5 + 5,M = 20 + 5,mod1 = 998244353,mod2 = 1e9 + 7,base1 = 131,base2 = 13331;
int f[N],siz[N],fa[N],v[N],dep[N],mdp[N];
int t[2][N];
ll res;
int find(int x){
	if(x != f[x]) f[x] = find(f[x]);
	return f[x];
}
void merge(int a,int b){
	int p = find(a),q = find(b);
	if(p == q) return;
	res += 1ll * t[1][q] * t[0][p];
	if(siz[p] > siz[q]) swap(p,q);
	f[p] = q;
	mdp[q] = min(dep[mdp[p]],dep[mdp[q]]) == dep[mdp[p]] ? mdp[p] : mdp[q];
	siz[q] += siz[p];
	t[0][q] += t[0][p];
	t[1][q] += t[1][p];
}
struct S{
	int pos,t0,t1;
	friend bool operator > (S j,S i){
		return 1ll * j.t1 * i.t0 > 1ll * i.t1 * j.t0; 
	}
};
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int n;
	cin>>n;
	for(int i=2;i<=n;i++){
		cin>>fa[i];
		dep[i] = dep[fa[i]] + 1;
	}
	priority_queue<S,vector<S>,greater<S> > pq;
	for(int i=1;i<=n;i++){
		f[i] = i;
		mdp[i] = i;
		siz[i] = 1;
		cin>>v[i];
		t[0][i] += v[i] == 0;
		t[1][i] += v[i] == 1;
		pq.push({i,t[0][i],t[1][i]});
	}
	while(pq.size() > 1){
		S tp = pq.top();
		pq.pop();
		int u = find(tp.pos),t0 = tp.t0,t1 = tp.t1;
		if(t0 != t[0][u] || t1 != t[1][u]){
			continue;
		}
		if(mdp[u] != 1){
			u = mdp[u];
			merge(u,fa[u]);
			int x = find(u);
			pq.push({x,t[0][x],t[1][x]});
		}	
	}
	cout<<res;
	return 0;
}
posted @ 2026-09-29 15:10  yutar  阅读(3)  评论(0)    收藏  举报