1. 程式人生 > 其它 >[2021.10.3NOIP模擬] 資料恢復

[2021.10.3NOIP模擬] 資料恢復

一般違法行為和犯罪之間沒有不可逾越的鴻溝!

前言

掉大坑了。

題目

連結就不給了。

題目大意

給定一棵 \(n\) 個點的樹,每個點上有兩個權值 \(a_i,b_i\),每次可以選擇一個點,條件是它的所有祖先都已經被選擇過,這樣可以組成一個排列 \(p_i\),令其代價為:

\[\sum_{i=1}^n(b_{p_i}\times \sum_{j=i+1}^na_{p_j}) \]

求最大代價。

\(1\le n\le 3\times 10^5;1\le a_i,b_i\le 5000;1\le f_i<i.\)

\(f_i\)\(i\) 節點的父親,似乎保證了 \(a_1=b_1=0.\)

樣例

樣例輸入1

4
1 1 2
0 0
3 1
5 1
4 1

樣例輸出1

14

不會還指望我再搬幾個樣例吧?

講解

首先我們不難推出優先選 \(\frac{b_i}{a_i}\) 最大的最優,這個只需要隨便寫個式子就好。

然後我就掉進了一個大坑,我覺得既然都有這個性質了,那麼正解一定是某個妙妙貪心,說不定有反悔什麼的。

然後我就無了。

如果不走偏的話其實正解也不難想,我們還是看上面那個東西,拋開祖先優先的限制不看,先選 \(\frac{b_i}{a_i}\) 最大的最優,但是如果有這個限制呢?

首先祖先一定是先選的,這毋庸置疑,然後你想選的這個最大值如果可以選了,一定會立即選它!所以我們可以考慮把最大值的那個點和它的祖先合併起來。

反覆合併,直到只剩一個點,每次合併產生 \(b_{F_i}\times a_i\)

的貢獻。

注意這裡的 \(F_i\) 不是 \(f_i\),因為它的父親可能已經和它的爺爺合併了,所以這個 \(F_i\) 是它的祖先中沒有被合併的最近的那個點,用並查集實現。

時間複雜度 \(O(n\log_2n)\),瓶頸在堆。

程式碼

//12252024832524
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define TT template<typename T>
using namespace std;

typedef long long LL;
const int MAXN = 300005;
int n,f[MAXN];
LL ans;

LL Read()
{
	LL x = 0,f = 1; char c = getchar();
	while(c > '9' || c < '0'){if(c == '-') f = -1;c = getchar();}
	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
	return x * f;
}
TT void Put1(T x)
{
	if(x > 9) Put1(x/10);
	putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
	if(x < 0) putchar('-'),x = -x;
	Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Min(T x){return x < 0 ? -x : x;}

LL a[MAXN],b[MAXN];
struct node
{
	LL a,b;int ID;
	bool operator < (const node &px)const{
		return b*px.a < px.b*a;
	}
};
priority_queue<node> q;

int F[MAXN];
int findSet(int x){if(F[x]^x)F[x] = findSet(F[x]);return F[x];}
void unionSet(int u,int v)
{
	u = findSet(u); v = findSet(v);
	if(u^v) F[u] = v;
}

int main()
{
//	freopen("data.in","r",stdin);
//	freopen("data.out","w",stdout);
	n = Read();
	for(int i = 1;i <= n;++ i) F[i] = i;
	for(int i = 2;i <= n;++ i) f[i] = Read();
	for(int i = 1;i <= n;++ i) a[i] = Read(),b[i] = Read();
	for(int i = 2;i <= n;++ i) q.push(node{a[i],b[i],i});
	while(!q.empty())
	{
		node t = q.top(); q.pop();
		if(t.a^a[t.ID] || t.b^b[t.ID]) continue;
		int fa = findSet(f[t.ID]);
		if(!fa) continue;
		unionSet(t.ID,fa);
		ans += b[fa] * a[t.ID];
		a[fa] += a[t.ID]; b[fa] += b[t.ID];
		q.push(node{a[fa],b[fa],fa});
	}
	Put(ans);
	return 0;
}