题解:AT_arc228_b [ARC228B] Minimize Topological Order

题目大意

给定一个排列 \(P\),和一个数组 \(A\)
对于一棵有根树 \(T\),定义好排列为满足所有父子约束的拓扑序,即父节点必须在子节点之前出现。
所有好排列中字典序最小的记为 \(f(T)\)
现在要找到所有 \(f(T) = P\) 的有根树 \(T\),并输出代价的最小值。
代价定义为求和,所有点的儿子个数的平方乘以 \(A_i\)

思路

核心思路: 按照 \(P\) 的顺序考虑每一个点,贪心地让它挂在代价最小的位置下面当儿子。

  • 根节点一定是 \(P_1\)

因为根节点没有父亲,所以它一定是第一个输出,也就是 \(P_1\)

  • 每个节点的父亲位置的范围。
    对每一个节点,它能挂的范围是在它前面第一个比它大的数到它的前一个。
    若我们现在考虑到的点是 \(P_u\)\(P_u\)前面第一个 \(P_v > P_u\) 的下标 \(v\) (\(v < u\)),那么现在 \(u\) 的父亲只能是 \([v, u - 1]\)
    右边界的范围是显然正确的,因为这样就保证了对于好的序列的限制(所选取的父亲一定在这个点之前出现)。

    关于左边界的范围:因为我们如果把 \(P_u\) 这个点挂到了 \(P_v\) 的子树外,那么我们扫到一个时候,因为 \(P_u < P_v\),会优先去选择 \(P_u\) 的方向,实际上 \(v < u\),我们应该需要先选 \(P_v\),所以为了避免这种情况,\(P_u\)\(P_v\),就一定要有祖孙关系制约或者来说至少不让它抢在 \(P_v\) 前面输出。

    反过来,如果现在 \(P_v < P_u\),那么 \(P_u\) 继续挂到 \(P_v\) 的上面的点的儿子并没有影响,因为现在如果 \(P_u\)\(P_v\) 做兄弟,那也会先去选 \(P_v\)

::::info[准确解释]
若将 \(P_u\) 挂在 \(P_v\) 之前的某个节点下面,那么 \(P_u\) 的父亲会先于 \(P_v\) 输出,这会导致 \(P_u\) 变为可用状态时,\(P_v\) 还没有输出,字典序就会选 \(u\)。我们限制 \(u\) 的选取范围不超过 \(v\),就保证了 \(P_u\) 至少不会抢在 \(P_v\) 前面输出。
::::

  • 接下来每一个点就只需要选它能挂到的点,并且找挂的最小代价。

实现

  • 对于每一个下标 \(i\),我们找到 \(P_i\) 这个点的父亲范围 \([q_i, i - 1]\),这个用单调栈就行了。
  • 对于一个下标 \(i\),如果 \(P_i\) 已经有 \(c_i\) 个儿子,那么再增加一个儿子的代价就是

\[A_{P_i}((c_i + 1) ^ 2 - c_i ^ 2) = A_{P_i}(2c_i + 1) \]

这个我们顺序扫过去,线段树查询区间内代价最小的点,把当前点送过去当儿子。
单点修改这个点的儿子数量更新它的代价。
同时加入这个当前点的代价,它的儿子数量为 \(0\)

  • 注意:先查询 \(i\),后加入 \(i\),因为 \(i\) 不能作为自己的父亲。

  • 总时间复杂度 \(O(N \log N)\)

代码

::::success[\(\text{code}\)]

#include<bits/stdc++.h>
#define int long long
#define fore(i, a, b) for( int i = (a); i <= (b); ++ i)
#define repe(i, a, b) for( int i = (a); i >= (b); -- i)
using namespace std;
const int N = 2e5 + 10;
int n, h[N], A[N];
int a[N];
int c[N];
int ans;
int q[N];
int st[N], top;
inline int ls(int p) {
	return (p << 1);
}
inline int rs(int p) {
	return ((p << 1) | 1);
}
struct Segtr{
	int t[N << 2], g[N << 2];
	void pushup(int p) {
		t[p] = min(t[ls(p)], t[rs(p)]);
		if(t[ls(p)] < t[rs(p)]) g[p] = g[ls(p)];
		else g[p] = g[rs(p)];
	}
	void modify(int p,int l,int r,int pos,int val) {
		if(l == r) {
			t[p] = val;
			g[p] = pos;
			return;
		}
		int mid = (l + r) >> 1;
		if(pos <= mid) modify(ls(p), l, mid, pos, val);
		else modify(rs(p), mid + 1, r, pos, val);
		pushup(p);
	}
	pair<int,int> query(int p,int l,int r,int L,int R) {
		if(L <= l && r <= R) {
			return {t[p], g[p]};
		}
		int mid = (l + r) >> 1;
		pair<int,int> res = {1e18, 0};
		if(L <= mid) {
			pair<int,int> tmp = query(ls(p), l, mid, L, R);
			if(tmp.first< res.first) res = tmp;
		}
		if(R >= mid + 1) {
			pair<int,int> tmp = query(rs(p), mid + 1, r, L, R);
			if(tmp.first < res.first) res = tmp;
		}
		return res;
	}
	void build(int p,int l,int r) {
		if(l == r) {
			t[p] = 1e18;
			g[p] = 0;
			return;
		}
		int mid = (l + r) >> 1;
		build(ls(p), l, mid);
		build(rs(p), mid + 1, r);
		pushup(p);
	}
}T;
signed main()
{
	ios::sync_with_stdio(false);
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	cin >> n;
	fore(i, 1, n) cin >> h[i];
	fore(i, 1, n) cin >> a[i];
	fore(i, 1, n) {
		A[i] = a[h[i]];
	}
	h[0] = 1e18;
	st[++ top] = 0;
	fore(i, 1, n) {
		while(top > 0 && h[i] > h[st[top]]) top --;
		q[i] = st[top];
		if(q[i] == 0) q[i] = 1;
		st[++ top] = i;
	}
	T.build(1, 1, n);
	T.modify(1, 1, n, 1, A[1] * (2 * c[1] + 1));
//	cout << "Q :";
//	fore(i, 1, n) cout << q[i] << ' ';
//	cout << '\n';
	fore(i, 2, n) {
		pair<int,int> res = T.query(1, 1, n, q[i], i - 1);
//		cout << "Res :";
//		cout << res.first << ' ' << res.second << '\n';
		ans = ans + res.first;
		int p = res.second;
		c[p] ++;
		T.modify(1, 1, n, p, A[p] * (2 * c[p] + 1));
		T.modify(1, 1, n, i, A[i] * (2 * c[i] + 1));
	}
	cout << ans << '\n';
	return 0;
}
posted @ 2026-09-10 19:03  wmq2012  阅读(7)  评论(0)    收藏  举报