牛客挑戰賽42 B 樹上啟發式合併
題目
(https://ac.nowcoder.com/acm/contest/6944/B)
題目描述
小睿睿和小熙熙是很好的朋友,他們的學校有n個教室,教室間有n-1條路徑且任意教室互相聯通,每個教室給他們帶來的愉悅值為val[i],
每天他們會選擇在兩個不同的教室(i,j)間的簡單路徑上秀恩愛,並給在lca(i,j)教室的人帶來gcd(val[i],val[j])的傷害。
每個教室裡的單身狗們想知道:能給他們帶來最大傷害和對應的無序點對數有多少個(對於葉子結點,最大傷害及對應的無序點對個數為0)
無序點對:(i,j)與(j,i)視作同一對,gcd(a,b):a與b的最大公因數
輸入描述:
第1行1個整數n,表示教室個數
第2至n行,每行兩個整數a,b,表示a,b間有一條邊
第n+1行,共n個整數,表示第i個教室的權值為val[i]
輸出描述:
共n行,每行2個整數,分別表示第i個點受到的最大傷害和對應的無序點對數
輸入
8
1 2
1 3
1 8
2 4
2 5
3 6
3 7
9 9 9 12 12 12 3 9
輸出
12 2
12 1
3 3
0 0
0 0
0 0
0 0
0 0
備註:
樹以1號教室為根
對於50%的資料,n≤1000且樹的形態隨機生成
另有20%的資料,樹的形態為一條鏈
對於100%的資料,n,val[i]≤100000
思路一 啟發式合併
我們考慮樹上啟發式合併,那麼對於一個樹,我們並不知道,他和存在的數誰的gcd最大。
那麼我們把數拆成他所有的因子。這樣我們就可以考慮所有的貢獻了,因為可能的gcd一個是他的因子。
現在就可以啟發式合併了,我剛開始考慮輕重鏈啟發式合併,發現一個問題,子樹間每個因子的對數和,必須記錄。
而這個是沒有辦法記錄的,後了考慮SET的合併,用一個mp記錄了可以。複雜度多個log,可以過了,
後面發現看題解發現
其實只記錄當前gcd的最大值和個數就可以了。如果現在的最大gcd為x,對數為y。合併這棵子數得到<a, b>
\(a<x\) 因為要先滿足gcd最大,所以這個b雖然沒有考慮之前的子樹貢獻,但是這個一定用不到。
\(a==x\) 那麼y記錄了之前的貢獻,b記錄這看子樹新產生的貢獻,那麼y+=b。就可以了。對數是正確的。
\(a>x\) 那麼y在之前一定沒有在兩棵子數出現過,不然最大的gcd不是x。所以y是正確的,用<x, y>替換<a, b>就可以了。
所以記錄最大的gcd和對數就可以了。
思路二 線段樹合併。
和上面的思路差不多,只是用線段樹來替換map。那麼在合併的同時,計算貢獻。
#pragma GCC optimize(3, "Ofast", "inline")
//#pragma GCC target("avx,avx2,fma")
//#pragma GCC optimization ("unroll-loops")
#include <bits/stdc++.h>
#define rint register int
#define LL long long
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
using namespace std;
char buf[1<<20],*p1=buf,*p2=buf;
inline LL read() {
char c=getchar();
LL x=0,f=1;
while(c<'0'||c>'9') {
if(c=='-')
f=-1;
c=getchar();
}
while(c>='0'&&c<='9') {
x=x*10+c-'0';
c=getchar();
}
return x*f;
}
inline void print(LL x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x >= 10)
print(x / 10);
putchar(x % 10 + '0');
}
struct edge {
int to, nex;
} e[100010 << 1];
int head[100010], tot;
void add(int x, int y) {
e[++tot].to = y;
e[tot].nex = head[x];
head[x] = tot;
}
int b[100010];
vector<int> v[100010];
pair<int, int> ans[100010];
map<int, LL, greater<int> > mp[100010];
pair<int, LL> sum[100010];
void DFS(int u, int fa) {
for(auto x: v[b[u]]) {
mp[u][x]++;
}
for(int i = head[u]; i; i = e[i].nex) {
int to=e[i].to;
if(to==fa) continue;
DFS(to, u);
if(mp[u].size()<mp[to].size()) { //啟發式貪心
swap(mp[u], mp[to]);
}
for(auto x: mp[to]) {
int s1=x.first;
LL s2=x.second;
if(mp[u].count(s1)){//計算新貢獻
if(s1==sum[u].first){
sum[u].second+=s2*mp[u][s1];
}
if(s1>sum[u].first){
sum[u]={s1, s2*mp[u][s1]};
}
break;
}
}
for(auto x: mp[to]) {//合併子鏈
int s1=x.first;
LL s2=x.second;
mp[u][s1]+=s2;
}
}
}
int main() {
int n, x, y;
n=read();
for(int i=1; i<n; i++) {
x=read(), y=read();
add(x, y);
add(y, x);
}
for(int i=1; i<=n; i++) {
b[i]=read();
}
for(int i=1; i<=n; i++) {
if(v[b[i]].size()==0) {
int sz=sqrt(b[i]);
for(int k=1; k<=sz; k++) {
if(b[i]%k==0) {
v[b[i]].push_back(k);
if(k*k!=b[i]) {
v[b[i]].push_back(b[i]/k);
}
}
}
}
}
DFS(1, 0);
for(int i=1; i<=n; i++) {
print(sum[i].first), printf(" "), print(sum[i].second), printf("\n");
}
return 0;
}