P2497 [SDOI2012] 基站建设

题意

平面上有很多点,第 \(i\) 个点的坐标为 \((x_i,0)\),每个点有两个属性 \(r_i,v_i\),分别表示祂的接受范围和选择代价。
现在你需要选择一些点 \(a_1,a_2,\dots,a_k\),满足 \(x_k+r_k\ge m,a_1=1\),且链接相邻两个点的代价的总和最小。
连接两个点 \((i,j)(i<j)\) 的代价为 \(\sqrt{r'}\),满足圆心在 \((x_i,r_i)\),半径为 \(r_i\) 的圆和圆心在 \(x_j,r'\),半径为 \(r'\) 的园相切。
选择某个点 \(i\) 的代价为 \(v_i\)
求最小的代价总和。
\(n\le 5\times10^5,x_i,m\le 10^{12},v_i\le 10^4\)

思路

考虑两个点 \((i,j)(i<j)\) 连接的代价。把那两个圆的圆心相连,变成一个直角三角形,斜边长度为 \(r_i+r'\),两条直角边分别为 \(x_j-x_i\)\(|r'-r_j|\)。根据勾股定理,有 \((r'-r_j)^2+(x_j-x_i)^2=(r_i+r')^2\),化简得 \(r'=\frac{(x_j-x_i)^2}{4r_i}\),代价即为 \(\frac{x_j-x_i}{2\sqrt{r_i}}\)
\(f_i\) 表示从 \(1\) 号点连到 \(i\)\(i\) 必须选)的最小代价。容易得到转移 \(f_i=\min_{j<i}(f_j+\frac{x_i-x_j}{2、sqrt{r_j}})\),用李超线段树优化一下即可。

代码

由于 \(x\) 很大,需要离散化。

/*
Luogu P2497 [SDOI2012] 基站建设
2026-04-16
*/
#include<bits/stdc++.h>
using namespace std;
namespace IO{
    template<typename T>
    inline void read(T&x){
        x=0;char c=getchar();bool f=0;
        while(!isdigit(c)) c=='-'?f=1:0,c=getchar();
        while(isdigit(c)) x=x*10+c-'0',c=getchar();
        f?x=-x:0;
    }
    template<typename T>
    inline void write(T x){
        if(x==0){putchar('0');return ;}
        x<0?x=-x,putchar('-'):0;short st[50],top=0;
        while(x) st[++top]=x%10,x/=10;
        while(top) putchar(st[top--]+'0');
    }
    inline void write(double x){printf("%lf",x);}
    inline void read(char&c){c=getchar();while(isspace(c)) c=getchar();}
    inline void write(char c){putchar(c);}
    inline void read(string&s){s.clear();char c;read(c);while(!isspace(c)&&~c) s+=c,c=getchar();}
    inline void write(string s){for(int i=0,len=s.size();i<len;i++) putchar(s[i]);}
    template<typename T>inline void write(T*x){while(*x) putchar(*(x++));}
    template<typename T,typename...T2> inline void read(T&x,T2&...y){read(x),read(y...);}
    template<typename T,typename...T2> inline void write(const T x,const T2...y){write(x),putchar(' '),write(y...),sizeof...(y)==1?putchar('\n'):0;}
}using namespace IO;
#define LL long long
const int maxn=500010;
const double inf=100000000000000000;
double f[maxn],ans=inf;
int n;
LL m,p[maxn];
struct node{LL x;int r,v,id;}a[maxn];
struct Line{
    double k,b;
    double calc(int w){double x=p[w];return k*x+b;}
};
class LICHAO_Segment_Tree{
private:
    Line t[maxn*8];
    void insert(int u,int l,int r,Line x){
        int mid=l+r>>1;
        if(x.calc(mid)<t[u].calc(mid)) swap(t[u],x);
        if(l==r) return ;
        if(x.calc(l)<t[u].calc(l)) insert(u<<1,l,mid,x);
        else insert(u<<1|1,mid+1,r,x);
    }
    double query(int u,int l,int r,int d){
        if(l>d||r<d) return inf;
        if(l==r) return t[u].calc(d);
        int mid=l+r>>1;
        return min({t[u].calc(d),query(u<<1,l,mid,d),query(u<<1|1,mid+1,r,d)});
    }
public:
    LICHAO_Segment_Tree(){for(int i=1;i<=maxn*4;i++) t[i]={0,inf};}
    void insert(Line x){insert(1,1,n,x);}
    double query(int x){return query(1,1,n,x);}
}t;
signed main(){
    read(n,m);
    for(int i=1;i<=n;i++) read(a[i].x,a[i].r,a[i].v),p[i]=a[i].x;
    sort(p+1,p+1+n);
    for(int i=1;i<=n;i++) a[i].id=lower_bound(p+1,p+1+n,a[i].x)-p;
    f[1]=a[1].v;
    t.insert({1/(2*sqrt(a[1].r)),f[1]-a[1].x/(2*sqrt(a[1].r))});
    for(int i=2;i<=n;i++){
        f[i]=t.query(a[i].id)+a[i].v;
        t.insert({1/(2*sqrt(a[i].r)),f[i]-a[i].x/(2*sqrt(a[i].r))});
    }
    for(int i=1;i<=n;i++) if(a[i].x+a[i].r>=m) ans=min(ans,f[i]);
    printf("%.3lf",ans);
    return 0;
}
posted @ 2026-04-16 20:29  Link-Cut_Trees  阅读(5)  评论(0)    收藏  举报