1. 程式人生 > 實用技巧 >[2020牛客暑期多校訓練營(第一場)虛樹 Infinite Tree]

[2020牛客暑期多校訓練營(第一場)虛樹 Infinite Tree]

2020牛客暑期多校訓練營(第一場)虛樹 Infinite Tree

題解參考部落格:https://blog.nowcoder.net/n/df889adfaf824d50ad2291f4d2eb04a2?&toCommentId=6480068

題目大意:

定義 \(mindiv(n)\)\(n\) 最小的大於1的約數,對於每一個 \(i\)\(1<=i<=m\) 建了一條邊 \((i,\frac{i}{mindiv[i]})\) ,給你一個 \(m\) ,表示用m個關鍵點,每一個關鍵點的值是 \(i!\) ,讓你用這m個關鍵點建一顆樹,然後給你 m個 \(wi\) 讓你求 \(min_u\sum_{i=1}^{m}wi*c(u,i!)\)

,其中 \(c(u,i!)\) 表示 \(u\) 這個點和 \(i\) 這個點之間的邊的數量,這個 \(u\) 則是這棵樹上的任意一個點。

題解:

這個題目前置技能點是虛樹,如果不會,先學習虛樹。

學習部落格:https://www.cnblogs.com/EchoZQN/p/13330893.html

學會虛樹了,然後開始分析這個題目。

  • 首先為什麼要用虛樹,這個是因為這棵樹太大了,無法存下來。

  • 怎麼建虛樹?

    • 建一棵虛樹需要什麼:原樹關鍵點的 \(dfs\) 序,原數關鍵點的 \(LCA\)
    • 這兩個知道之後,就可以直接套模板了
  • 怎麼求原樹關鍵點的 \(dfs\) 序?

    • 首先我們比較 \(i!\)

      \((i+1)!\)

    • 我們定義 \(dfs\) 序:

    • \((i+1)!\) 相對於 \(i!\) ,多了一個約數 \((i+1)\) ,如果這個 \((i+1)\) 是一個質數,是不是 \((i+1)!\)\(dfs\) 序一定在 \(i!\) 後面,如果這個不是一個質數,那麼如果 \(i+1\) 存在約數大於 \(i!\) 的最小素因子,那麼是不是 \((i+1)!\) 會分向右分叉,如果沒有,那就是都等於最小素因子,那麼是不是會延長,所以 \((i+1)!\)\(dfs\) 序一定大於 \(i!\)

    • 所以最終結果就是對於這 \(m\) 個關鍵點,他們的 \(dfs\)

      序和本身大小成正比。

  • 接下來說說怎麼求 \(LCA\)

    • 假設 \(u=p_{1}^{a1}p_{2}^{a1}...p_{x}^{ax}\) \(v=p_{1}^{a1}p_{2}^{a2}...p_{y}^{ay}\) (p按照順序排序)

    • 那麼,他們相同的節點就是從後往前比較第一個不同的位置,也就是第一個 \((p_i!=p_j||ai!=aj)\) 這個位置就是他們的 \(LCA\)

    • 因為 \(dfs\) 序是和本身大小成正比的,所以對於數 \(i!\) 和 $(i+1)! $ ,假設 \(lca1=LCA((i-1)!,i!)\)\(lca2=LCA(i!,(i+1)!)\) 只有三種關係:

      • \(lca1==lca2\) 直接建邊
      • \(lca2<lca1\) 重新給這個 \(lca\) 一個編號,然後建邊
      • \(lca2=1\) 直接建邊
  • 最後總結一下怎麼求 \(LCA\)

    • 首先用線段樹維護一下這個位置和棧頂元素的 \(LCA\) 的深度
    • 這個維護參考上面。
    • 假設當前位置是 \(p\) ,棧頂元素是 \(x\) ,棧第二個元素是 \(y\)
    • 先求 \(lca=LCA(p,x)\)
      • \(dep[lca]==dep[y]\) 說明 \(lca=y\)
      • \(dep[lca]>dep[y]\) 則說明 \(lca\) 還在 \(y\) 這個節點之上,丟掉棧頂元素,繼續判斷
      • \(dep[lca]<dep[y]\) 說明 \(lca\) 還沒有入棧,那就給這個 \(lca\) 一個編號,然後讓他入棧,建邊
#include <bits/stdc++.h>
#define fir first
#define sec second
#define inf 0x3f3f3f3f
#define inf64 0x3f3f3f3f3f3f3f3f
#define debug(x) printf("debug:%s=%d\n",#x,x);
//#define debug(x) cout << #x << ": " << x << endl
using namespace std;
const int maxn = 2e5+10;
typedef long long ll;
int head[maxn<<1],nxt[maxn<<1],to[maxn<<1],cnt;
void ADD(int u,int v){
//    printf("u=%d v=%d\n",u,v);
    ++cnt,to[cnt]=v,nxt[cnt]=head[u],head[u]=cnt;
    ++cnt,to[cnt]=u,nxt[cnt]=head[v],head[v]=cnt;
}

int sum[maxn*4];//Ï߶ÎÊ÷ά»¤lca
void build(int id,int l,int r){
    sum[id]=0;
    if(l==r) return ;
    int mid=(l+r)>>1;
    build(id<<1,l,mid);
    build(id<<1|1,mid+1,r);
}
void update(int id,int l,int r,int pos,int val){
    if(l==r){
        sum[id]+=val;
        return ;
    }
    int mid=(l+r)>>1;
    if(pos<=mid) update(id<<1,l,mid,pos,val);
    else update(id<<1|1,mid+1,r,pos,val);
    sum[id]=sum[id<<1]+sum[id<<1|1];
}

int query(int id,int l,int r,int x,int y){
    if(x<=l&&y>=r) return sum[id];
    int mid=(l+r)>>1,ans=0;
    if(x<=mid) ans+=query(id<<1,l,mid,x,y);
    if(y>mid) ans+=query(id<<1|1,mid+1,r,x,y);
    return ans;
}

int dep[maxn],w[maxn],stk[maxn],cur,now,n;
int isp[maxn],tot,v[maxn];
void init() {
    tot = 0;
    memset(v,0,sizeof(v));
    for (int i = 2; i < maxn; ++i) {
        if (!v[i]) {
            v[i] = i;
            isp[++tot] = i;
        }
        for (int j = 0; j < tot; ++j) {
            if (1ll * i * isp[j] >= maxn) break;
            v[i * isp[j]] = isp[j];
        }
    }
}
int num;
pair<int,int>prime[maxn];
void judge(int x) {
    int y = x;
    num = 0, dep[y] = 0;
    int f = lower_bound(isp + 1, isp + 1 + tot, 500) - isp;
    for (int i = 1; i <= f; i++) {
        int cur = 0;
        while (x % isp[i] == 0) x /= isp[i], cur++;
        if (cur) prime[++num] = make_pair(i, cur);
        dep[y] += cur;
        if (x == 1) break;
    }
    if (x != 1) {
        dep[y]++;
        int pos = lower_bound(isp + 1, isp + 1 + tot, x) - isp;
        prime[++num] = make_pair(pos, 1);
    }
//    debug(y)
//    debug(dep[y])
}

void modify(){
	for(int i=1;i<=num;i++) update(1,1,n,prime[i].fir,prime[i].sec);
}

void insert(int p) {
//	printf("p=%d\n",p);
    judge(p);
    dep[p]+=dep[p-1];
//    printf("dep[%d]=%d\n",p,dep[p]);
    int x = stk[cur];
    if (x == 1) {
    	modify();
        stk[++cur] = p;
        return;
    }
    int pos = prime[num].fir;
    int d = query(1, 1, n, pos, n);
//    debug(d)
//    debug(dep[stk[cur]])
    while (d != dep[stk[cur]]) {
        int y = stk[--cur];
//        debug(y)
        if(dep[y]<d){
        	++now,dep[now]=d;
			ADD(now,x),stk[++cur]=now;
            break;
        }
        if(dep[y] == d){
        	ADD(y,x);
        	break;
		}
        ADD(x,y),x=stk[cur];
    }
//    debug(cur)
//    debug(stk[cur])
    stk[++cur]=p;
    modify();
}

int m;
ll dp[maxn],have[maxn],ans;

void dfs1(int u,int pre){
//	printf("u=%d\n",u);
    dp[u]=0,have[u]=0;
    if(u<=m&&u>=1) have[u]=w[u];
    for(int i=head[u];i;i=nxt[i]){
        int v = to[i];
        if(v==pre) continue;
        dfs1(v,u);
        have[u]+=have[v];
        ll dis = dep[v]-dep[u];
//        printf("u=%d v=%d dis=%lld\n",u,v,dis);
        dp[u]+=dp[v]+have[v]*dis;
    }
//    printf("dp[%d]=%lld\n",u,dp[u]);
}

void dfs2(int u,int pre){
//	debug(u);
	ans=min(ans,dp[u]);
    for(int i=head[u];i;i=nxt[i]){
        int v = to[i];
        if(v==pre) continue;
        ll dis = dep[v]-dep[u];
//        printf("dp[%d]=%lld dp[%d]=%lld have[%d]=%d have[%d]=%d dis=%lld\n",u,dp[u],v,dp[v],u,have[u],v,have[v],dis);
        dp[v]=dp[u]-have[v]*dis+(have[u]-have[v])*dis;
//        printf("dp[%d]=%lld\n",v,dp[v]);
        have[v]=have[u];
        dfs2(v,u);
    }
	have[u]=0,head[u]=0;
}

void solve(){
    now=m,cnt=0,stk[cur=1]=1;
    for(int i=2;i<=m;i++) insert(i);
    while(--cur) ADD(stk[cur],stk[cur+1]);
	ans = inf64;
    dfs1(1,0),dfs2(1,0);
    printf("%lld\n",ans);
    return ;
}

int main(){
    init();
    while(scanf("%d",&m)!=EOF){
        n = lower_bound(isp+1,isp+1+tot,m)-isp;
        for(int i=1;i<=m;i++) scanf("%d",&w[i]);
        build(1,1,n);solve();
    }
}