LGP6074 秘术「天文密葬法」 学习笔记
LGP6074 秘术「天文密葬法」 学习笔记
前言
是的是的,在洛谷,大家都叫我“最小路径”;
但是在(已神隐的)COGS OJ,我还有一个名字,大家都叫我“秘术「天文密葬法」”啊!
符卡介绍
东方永夜抄6A面五符。
只是用了很大气的名字,毫无理由地玩弄着天空的永琳。
与其说是把地上变成了一个巨大的密室,怎么想都觉得月球才应该是密室。
月之使者在平流层烧光了吗,找错道路。永琳所准备的虚假之月比真正的月亮还要豪华。
但是,幽幽子曰「古老的月亮」的样子。过去比现在要豪华?宇宙当中有着浪漫呢。但是轮椅上的男人的宇宙让人费解。
他的理论除了他自己已经无人能够将其订正。如果是妄想的话那真太厉害了。
题意简述
给定一棵 \(n\) 个结点,以 \(1\) 为根的树。每个结点 \(i\) 有两个权值 \(a_i,b_i\)。试找一条长度为 \(m\) 的简单路径最小化 \(\frac{\sum a_i}{\sum b_i}\)。输出这个值,保留两位小数。
\(1\le m\le n\le 2\times 10^5,1\le a_i,b_i\le 2\times 10^3\)。
做法解析
首先这是典型的分数规划,我们二分答案。对于每次二分的 \(mid\),我们对于每个点 \(u\) 设一个点权 \(val_u=a_i-mid\times b_i\),则当前 \(mid\) 合法的前提是:存在一条长度为 \(m\) 的简单路径,其点权和小于 \(0\)。
推导过程:
\(\because \frac{\sum a_i}{\sum b_i}\le mid\)
\(\therefore \sum a_i\le mid\sum b_i\)
于是我们现在要做 \(\log V\) 次这个问题:给定一棵树,问长度为 \(m\) 的树上路径的最小点权和。
先不管时间复杂度设计一个 DP。众所周知树上的任何链都可以视作在起终点 LCA 处的两条(或一条)分链,所以不难想到设 \(f_{u,i}\) 为 \(u\) 向下长度为 \(i\) 的链的最小点权和。转移显然。
当前时间复杂度 \(O(n^2\log V)\)。我们肯定是期望拿长剖干掉一个 \(n\) 的,不过这么做有个问题,现在 \(f_{v,i}\)(这个 \(v\) 是长儿子)转移到 \(f_{u,i+1}\) 全都要加上一个 \(val_u\),很麻烦。解决办法很简单:改设 \(f_{u,i}\) 为从根到 \(u\) 的 \(i\) 级儿子的链的最小点权和。计算答案时扣除掉多算的部分就行了(相关地,你将看到代码里把 \(val\) 处理成了前缀和状物)。
代码实现
#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
const int MaxN=2e5+5;
int N,M,X,Y,A[MaxN],B[MaxN];
vector<int> Tr[MaxN];
void addudge(int u,int v){
    Tr[u].push_back(v);
    Tr[v].push_back(u);
}
int dson[MaxN],edis[MaxN];
void dfs1(int u,int f){
    for(auto v : Tr[u]){
        if(v==f)continue;dfs1(v,u);
        if(edis[v]>edis[dson[u]])edis[u]=edis[v],dson[u]=v;
    }
    edis[u]=edis[dson[u]]+1;
}
int dfn[MaxN],dcnt;
doub pool[MaxN],*dp[MaxN],val[MaxN],res;
void dfs2(int u,int f){
    dfn[u]=++dcnt,dp[u]=pool+dfn[u];
    if(dson[u])dfs2(dson[u],u);
    for(int v : Tr[u])if(v!=f&&v!=dson[u])dfs2(v,u);
}
void dfs3(int u,int f){
    val[u]+=val[f],dp[u][0]=val[u];
    if(dson[u])dfs3(dson[u],u);
    for(int v : Tr[u]){
        if(v==f||v==dson[u])continue;dfs3(v,u);
        for(int i=0;i<edis[v];i++)if(M-(i+1)>=0&&edis[u]>M-(i+1))minner(res,dp[u][M-(i+1)]+dp[v][i]-val[u]-val[f]);
        for(int i=0;i<edis[v];i++)minner(dp[u][i+1],dp[v][i]);
    }
    if(edis[u]>M)minner(res,dp[u][M]-val[f]);
}
bool check(doub lim){
    res=Inf,dcnt=0;
    for(int i=1;i<=N;i++)val[i]=A[i]-lim*B[i];
    for(int i=1;i<=N;i++)pool[i]=Inf;
    dfs3(1,0);return res<=0;
}
int main(){
    readis(N,M);
    for(int i=1;i<=N;i++)readi(A[i]);
    for(int i=1;i<=N;i++)readi(B[i]);
    for(int i=1;i<N;i++)readis(X,Y),addudge(X,Y);
    dfs1(1,0),dfs2(1,0);
    doub sl=0,sr=2000,smid,ans=-1;
    while(sl<=sr-eps){
        smid=(sl+sr)/2;
        if(check(smid))ans=smid,sr=smid-eps;
        else sl=smid+eps;
    }
    if(ans<0)writi(-1);
    else printf("%.2lf",ans);
    return 0;
}
后记
我奥林龙就是写挂,挂样例,对拍调半小时,不会用你们一个指针!
哎嘛,真香。
你不得不承认,长剖相关题目这块,开个指针数组它确实好用啊!
                    
                
                
            
        
浙公网安备 33010602011771号