[BZOJ2402]-陶陶的难题II-二分答案+线段树上凸包

本文介绍了一种解决特定树形结构问题的方法,通过树形DP结合线段树维护凸包技巧,高效解答关于路径上两点间特殊函数最大值的查询问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

说在前面

早上考试考到这道题
没想一会想到一个 nlog4n n log 4 ⁡ n 的做法,感觉药丸
然后听见出题人小声一句: log4n log 4 ⁡ n 是对的。嗯,然后开始码码码
真·长,还被数据卡精度emmmm


题目

BZOJ2402传送门

题目大意

给出一棵 n n 个节点的树,每个节点有四个权值:xi  yi  pi  qi
现在需要回答 m m 个询问,每个询问大概长这样:在 u,v 两点的链上,选出两个点 i,j i , j ,使得 yi+qixi+pi y i + q i x i + p i 最大
范围: n,m3104 n , m ≤ 3 ∗ 10 4 xi, yi, pi, qi105 x i ,   y i ,   p i ,   q i ≤ 10 5
约定:输出误差不能超过 103 10 − 3

输入输出格式

输入格式:
第一行一个正整数 n n ,含义如题
第二,三,四,五行分别包含 n 个正实数。第 i i 个数分别表示表示 xi, yi, pi, qi

接下来 n1 n − 1 行,每行两个正整数 a,b a , b ,描述一条树边
接下来一行包含一个数 m m ,表示询问的个数
最后 m 行,每行包含正整数 u,v u , v ,表示一次询问

输出格式:
对于每个询问,输出一行一个实数表示答案qwq


解法

把式子一化,发现是个 类似分数规划的形式,然后显然二分答案
然后二分的那个值看起来就和斜率一样,所以式子变成了 同一个斜率的线 在两个凸包上切得的最大值之和

然后就用线段树维护凸包就好了…每个节点开一个vector,当前凸包上的点一定是两个儿子凸包上的点,归并一下即可

注意我们不需要合并信息,因为我们要的是切凸包最大值,这个大凸包一定是由覆盖到的区间的小凸包上的点组成,所以我们在查询时候,把各个覆盖到的区间的凸包都询问一遍就好


下面是代码

#include <ctime>
#include <vector>
#include <cstdio>
#include <cstring>
#include <algorithm>
//#define Izumihanako
using namespace std ;

double eps = 1e-9 , MAXK = 0 ;
int N , M , tp , head[30005] , stt ;

struct Point{
    double x , y ;
} p1[30005] , p2[30005] , t[100005] ;

struct Path{
    int pre , to ;
} p[60005] ;

typedef Point Vector ;
Vector operator - ( const Vector &A , const Vector &B ){ return ( Vector ){ A.x - B.x , A.y - B.y } ; }
double cross( const Vector &A , const Vector &B ){ return A.x * B.y - A.y * B.x ; }
double slope( const Vector &A ){ return A.y / A.x ; }
int sign( const double &x ){
    if( x < eps && x > -eps ) return 0 ;
    return x > eps ? 1 : -1 ;
}

void In( int t1 , int t2 ){
    p[++tp] = ( Path ){ head[t1] , t2 } ; head[t1] = tp ;
    p[++tp] = ( Path ){ head[t2] , t1 } ; head[t2] = tp ;
}

void smax( double &x , double y ){ if( x < y ) x = y ; }
struct Data{
    double v1 , v2 ;
    inline void update( const Data &A ){
        if( v1 < A.v1 ) v1 = A.v1 ;
        if( v2 < A.v2 ) v2 = A.v2 ;
    }
} ;

struct Node{
    Node *ch[2] ;
    int siz1 , siz2 ;
    vector<Point> cvx1 , cvx2 ;
    void Merge( vector<Point> &L , vector<Point> &R ){
        stt = 0 ;
        int Ls = L.size() , Rs = R.size() , lp = 0 , rp = 0 ;
        while( lp < Ls || rp < Rs ){
            if( rp == Rs || ( lp != Ls && L[lp].x < R[rp].x ) )
                t[++stt] = L[lp] , lp ++ ;
            else t[++stt] = R[rp] , rp ++ ;
        }
    }
    void calcu_cvx(){
        int ba = -1 ; this->Merge( ch[0]->cvx1 , ch[1]->cvx1 ) ;
        for( int i = 1 ; i <= stt ; i ++ ){
            while( ba >= 1 && sign( cross( cvx1[ba] - cvx1[ba-1] , t[i] - cvx1[ba-1] ) ) >= 0 )
                cvx1.pop_back() , ba -- ;
            cvx1.push_back( t[i] ) , ba ++ ;
        } while( ba >= 1 && sign( cvx1[ba].y - cvx1[ba-1].y ) <= 0 ) cvx1.pop_back() , ba -- ;
        siz1 = ba + 1 ;

        ba = -1 , this->Merge( ch[0]->cvx2 , ch[1]->cvx2 ) ;
        for( int i = 1 ; i <= stt ; i ++ ){
            while( ba >= 1 && sign( cross( cvx2[ba] - cvx2[ba-1] , t[i] - cvx2[ba-1] ) ) >= 0 )
                cvx2.pop_back() , ba -- ;
            cvx2.push_back( t[i] ) , ba ++ ;
        } while( ba >= 1 && sign( cvx2[ba].y - cvx2[ba-1].y ) <= 0 ) cvx2.pop_back() , ba -- ;
        siz2 = ba + 1 ;
    }
} *root , w[60005] , *tw = w ;

int siz[30005] , dep[30005] , son[30005] , fa[30005] ;
int in[30005] , arc[30005] , dfs_c , top[30005] ;

void dfs1( int u ){
    for( int i = head[u] ; i ; i = p[i].pre ){
        int v = p[i].to ;
        if( v == fa[u] ) continue ;
        fa[v] = u , dep[v] = dep[u] + 1 ;
        dfs1( v ) , siz[u] += siz[v] ;
        if( siz[ son[u] ] < siz[v] ) son[u] = v ;
    } siz[u] ++ ;
}

void dfs2( int u , int tp ){
    in[u] = ++dfs_c , arc[dfs_c] = u , top[u] = tp ;
    if( son[u] ) dfs2( son[u] , tp ) ;
    for( int i = head[u] ; i ; i = p[i].pre ){
        int v = p[i].to ;
        if( v == fa[u] || v == son[u] ) continue ;
        dfs2( v , v ) ;
    }
}

Node *build( int lf , int rg ){
    Node *nd = ++tw ;
    if( lf == rg ){
        nd->cvx1.push_back( p1[ arc[lf] ] ) ;
        nd->cvx2.push_back( p2[ arc[lf] ] ) ;
        nd->siz1 = nd->siz2 = 1 ; return nd ;
    } int mid = ( lf + rg ) >> 1 ;
    nd->ch[0] = build( lf , mid ) ;
    nd->ch[1] = build( mid+1,rg ) ;
    nd->calcu_cvx() ; return nd ;
}

double K ; int L , R ;
double cut_cvx( vector<Point> &cvx , int siz ){
    int lf = 0 , rg = siz - 2 , ans = siz - 1 , mid ;
    while( lf <= rg ){
        mid = ( lf + rg ) >> 1 ;
        double slope = ::slope( cvx[mid+1] - cvx[mid] ) ;
        if( sign( K - slope ) > 0 ) ans = mid , rg = mid - 1 ;
        else lf = mid + 1 ;
    } return cvx[ans].y - cvx[ans].x * K ;
}

Data Query( Node *nd , int lf , int rg ){
    if( L <= lf && rg <= R )
        return ( Data ){ cut_cvx( nd->cvx1 , nd->siz1 ) , cut_cvx( nd->cvx2 , nd->siz2 ) } ;
    int mid = ( lf + rg ) >> 1 ; Data rt = ( Data ){ -1e20 , -1e20 } ;
    if( L <= mid ) rt.update( Query( nd->ch[0] , lf , mid ) ) ;
    if( R >  mid ) rt.update( Query( nd->ch[1] , mid+1,rg ) ) ;
    return rt ;
}

double Jump( int u , int v ){
    Data rt = ( Data ){ -1e20 , -1e20 } ;
    while( top[u] != top[v] ){
        if( dep[ top[u] ] < dep[ top[v] ] ) swap( u , v ) ;
        L = in[ top[u] ] , R = in[u] , rt.update( Query( root , 1 , N ) ) ;
        u = fa[ top[u] ] ;
    } if( dep[u] < dep[v] ) swap( u , v ) ;
    L = in[v] , R = in[u] , rt.update( Query( root , 1 , N ) ) ;
    return rt.v1 + rt.v2 ;
}

double qvq( int u , int v ){
    double lf = 0 , rg = MAXK , res ;
    while( rg - lf >= 1e-4 ){
        K = ( lf + rg ) / 2 ;
        res = Jump( u , v ) ;
        if( sign( res ) > 0 ) lf = K ;
        else rg = K ;
    } return ( lf + rg ) / 2 ;
}

void preWork(){
    dfs1( 1 ) , dfs2( 1 , 1 ) ;
    root = build( 1 , N ) ;
}

void solve(){
    scanf( "%d" , &M ) ;
    for( int i = 1 , u , v ; i <= M ; i ++ ){
        scanf( "%d%d" , &u , &v ) ;
        printf( "%f\n" , qvq( u , v ) ) ;
    }
}

int main(){
#ifdef Izumihanako
    freopen( "out.txt", "w" , stdout) ;
    freopen( "in.txt" , "r" , stdin ) ;
#endif
    scanf( "%d" , &N ) ;
    for( int i = 1 ; i <= N ; i ++ ) scanf( "%lf" , &p1[i].x ) ;
    for( int i = 1 ; i <= N ; i ++ ) scanf( "%lf" , &p1[i].y ) , smax( MAXK , p1[i].y / p1[i].x ) ;
    for( int i = 1 ; i <= N ; i ++ ) scanf( "%lf" , &p2[i].x ) ;
    for( int i = 1 ; i <= N ; i ++ ) scanf( "%lf" , &p2[i].y ) , smax( MAXK , p2[i].y / p2[i].x ) ;
/*
    for( int i = 1 ; i <= N ; i ++ ){
        scanf( "%lf%lf" , &p1[i].x , &p1[i].y ) , smax( MAXK , p1[i].y / p1[i].x ) ;
        scanf( "%lf%lf" , &p2[i].x , &p2[i].y ) , smax( MAXK , p2[i].y / p2[i].x ) ;
    }
*/  
    for( int i = 1 , u , v ; i < N ; i ++ )
        scanf( "%d%d" , &u , &v ) , In( u , v ) ;
    preWork() ; solve() ;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值