1. 程式人生 > >[COGS2652]祕術「天文密葬法」-長鏈剖分-01分數規劃

[COGS2652]祕術「天文密葬法」-長鏈剖分-01分數規劃

祕術「天文密葬法」

題目說明:
路徑的長度是點數
所有整數都是正整數
已新增一句話題意

【題目描述】

永琳需要協助紫解決異變!
在某個滿月的夜晚,幻想鄉的結界出現了異常,雖然目前還沒有找到原因,不過有一點可以肯定的是,這次異變一定和滿月有關。間隙妖怪紫在試圖修復結界時需要永琳幫她排除滿月產生的干擾,為了保護輝夜公主,永琳必須協助紫解決這次異變,所以她打算再次使用符卡”祕術「天文密葬法」”來用虛假的月亮替換真實的滿月,但是她在使用符卡的時候出現了一些問題。
“祕術「天文密葬法」”由n個使魔組成,每個使魔都有一個能值和一個波值,同時存在n-1條能量通道將這n個使魔連線起來,並且每個使魔都能通過能量通道和其它所有使魔相連。
完成天文密葬法的關鍵步驟是在這n個使魔中找到一條用能量通道連線起來的路徑,將大部分能量集中於這條路徑來展開法術,然而路徑上的使魔在法術張開時會產生共振,產生一個干擾值,干擾值等於路徑上所有使魔能值的和除以波值的和。
為了確保計劃順利進行,永琳需要選擇一條長度為m且干擾值最小的路徑,雖然作為月之頭腦,但此時永琳需要集中精力展開法術,所以她向你求助。
永琳在知道一個干擾值後就能快速找到這個干擾值對應的路徑,你只需要告訴她所有路徑中干擾值最小的路徑干擾值是多少
答案四捨五入到小數點後兩位
一句話題意:
給個樹,第i個點有兩個權值ai和bi,現在求一條長度為m的路徑,使得Σai/Σbi最小

【輸入格式】

第一行一個整數n,m,意義如上
如果m為-1則表示對長度沒有限制(但路徑不能為空
第二行n個整數,第i個整數ai表示第i個使魔的能值
第三行n個整數,第i個整數bi表示第i個使魔的波值
接下來n-1行,每行兩個整數l,r,表示有一條能量路徑連線第l個使魔和第r個使魔
一行中的所有整數均用空格隔開

【輸出格式】

如果不存在長度為m的鏈,請輸出-1
否則一行一個浮點數,表示干擾值最小的路徑干擾值是多少

【樣例輸入1】

3 2
2 3 3
6 6 6
1 2
2 3

【樣例輸出1】

0.42

【樣例輸入2】

9 3
9 4 4 1 6 5 1 9 5
8 3 3 1 5 4 1 8 4
1 2
2 3
3 4
3 5
1 6
6 7
7 8
6 9

【樣例輸出2】

1.15

【資料範圍】

m<=n<=200000
ai,bi<=200000

《論根據名字選擇題目的優越性》
看見名字直接被炸進來了……

思路:

ans=aibi

可以很明顯發現這是一個裸的01分數規劃……
那麼就變個形

aiansbi=0

然後就是慣用的二分答案了~

然後顯然看到路徑統計就會想到點分治……
然而點分治總複雜度為O(nlog2n),能過但跑的太慢,不夠優秀。

不如考慮一種奇妙的方法——長鏈剖分。
長鏈剖分是樹鏈剖分的一種,平時一般說樹鏈剖分指的都是重鏈剖分。
長鏈剖分和重鏈剖分唯一的不同在於,長鏈剖分以子樹中最深點的深度作為剖分的依據,而重鏈剖分則是以子樹內點的個數為依據。
在重鏈剖分的情況下,如果進行一波dsu on tree,複雜度會是O

(nlogn)的。
然而當題目的詢問關鍵字為點的深度時,可以改為使用長鏈剖分,複雜度為O(n)
為什麼呢?因為咱每次用的都是每個兒子的長鏈進行更新,那麼如果一條長鏈被算進了複雜度,那麼就說明它不是它的父親的長鏈,它的祖先就再也不會用到它了。
這樣可以發現每個點被暴力統計的次數為O(1),總複雜度O(n)

這題怎麼用長鏈剖分呢?
剛才咱已經將問題轉換為了判斷樹上長度為m的路徑得到的上式的值的最小值為多少,用這個值去二分。
可考慮一個類似點分治的,很暴力的dp:
f[i][j]為以i為根的子樹中,相對深度為j的路徑的最小值。
那麼很顯然需要使用每個兒子的f值以更新當前點的f值。
可以用同一點上,長度加起來為m的兩個f值之和更新答案。

考慮到空間開不下,選擇只開一維,預處理出dfs序,令f[i]表示當前點向下相對深度為i的路徑最小值。
對於每個節點,秉承著不浪費每一絲空間的原則,每次咱取一段f上的位置以供當前節點使用。
如何確定這m個位置呢?
如果使用長鏈剖分,那麼就是從其dfs序上的位置開始向後m個位置。
因為可以發現,在長鏈剖分下,重兒子的dfs序編號必然比當前節點大1。
那麼重兒子分配到的會是從當前點的dfs序編號+1處開始,長度為m的一段陣列。
而在樸素dp中,當前節點繼承的兒子的每個f值恰好是其兒子的f值中所有下標整體+1,新增一個f[0],同時給每個位置加上當前節點的點權。
也就是說,直接從當前點在dfs序上的位置開始取m個,並對這m個位置進行區間加,即可得到當前節點的f陣列!
顯然如果長度在f與最深相對深度取最小值的情況下,這是不會存在相交情況的。

那麼這就做到了O(1)繼承重兒子,同時每個深度的節點也只被合併了一次,複雜度是O(n)的!

那麼這就做完了~
因為小小地卡了一波常數,可能程式碼看起來有點醜……
相應的成果是,短時間內上榜了~
估計也就是短時間內能上了……

#include<cstdio>
#define getchar getchar_unlocked

using namespace std;

typedef float db;
const int N=2e5+9;
const db eps=1e-3;

int n,m;
int to[N<<1],nxt[N<<1],beg[N],tot;
int id[N],dep[N],siz[N],son[N],dfn;
int a[N],b[N],dsiz[N],fl;
db inc,tag[N],dp[N],ret;

inline int read()
{
    int x=0;char ch=getchar();
    while(ch<'0' || '9'<ch)ch=getchar();
    while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
    return x;
}

inline void chkmin(db &a,db b){if(a>b)a=b;}

inline void add(int u,int v)
{
    to[++tot]=v;
    nxt[tot]=beg[u];
    beg[u]=tot;
    to[++tot]=u;
    nxt[tot]=beg[v];
    beg[v]=tot;
}

void dfs1(int u,int fa)
{
    siz[u]=dep[u];
    son[u]=0;
    for(register int i=beg[u],v;i;i=nxt[i])
        if((v=to[i])!=fa)
        {
            dep[v]=dep[u]+1;
            dfs1(v,u);
            if(siz[u]<siz[v])
                siz[u]=siz[v],son[u]=v;
        }
    dsiz[u]=siz[u]-dep[u];
}

void dfs2(int u,int fa)
{
    id[u]=++dfn;
    if(son[u])
    {
        dfs2(son[u],u);
        for(register int i=beg[u],v;i;i=nxt[i])
            if((v=to[i])!=fa && v!=son[u])
                dfs2(v,u);
    }
}

void dfs(int u,int fa)
{
    db cost=(db)a[u]-inc*(db)b[u];
    db *f=&dp[id[u]],*ff;

    if(!son[u])
    {
        f[0]=cost;
        tag[u]=0;
        if(!m)
            chkmin(ret,cost);
        return;
    }

    dfs(son[u],u);
    f[0]=-tag[son[u]];
    tag[u]=tag[son[u]]+cost;

    for(register int i=beg[u],v;i;i=nxt[i])
        if((v=to[i])!=fa && v!=son[u])
        {
            dfs(v,u);
            ff=&dp[id[v]];
            for(register int j=0;j<=dsiz[v] && j<m;j++)
                if(m-1-j<=dsiz[u])
                    chkmin(ret,f[m-1-j]+ff[j]+tag[u]+tag[v]);
            for(register int j=0;j<=dsiz[v];j++)
                chkmin(f[j+1],ff[j]+tag[v]-tag[u]+cost);
        }

    if(dsiz[u]>=m)
        chkmin(ret,f[m]+tag[u]);
}

inline bool judge(db x)
{
    inc=x;
    ret=1e18;
    dfs(1,0);
    if(ret!=1e18)fl=1;
    return ret<=0;
}

int main()
{
    freopen("cdcq_b.in","r",stdin);
    freopen("cdcq_b.out","w",stdout);

    n=read();m=read()-1;
    for(int i=1;i<=n;i++)
        a[i]=read();
    for(int i=1;i<=n;i++)
        b[i]=read();

    db l=1e18,r=1e2,mid;
    for(int i=1;i<=n;i++)
        chkmin(l,(db)a[i]/(db)b[i]);

    if(m==-2)
    {
        printf("%.2f\n",l);
        return 0;
    }

    for(int i=1;i<n;i++)
        add(read(),read());

    dfs1(1,0);
    dfs2(1,0);

    if(!judge(r))
    {
        puts("-1");
        return 0;
    }

    while(r-l>eps)
    {
        mid=(l+r)/2.0;
        if(judge(mid))
            r=mid;
        else
            l=mid;
    }

    printf("%.2f\n",l);
    return 0;
}