1 条题解

  • 0
    @ 2025-8-24 21:47:53

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar lhm_
    sjzez 的一名 OI 学生

    搬运于2025-08-24 21:47:53,当前版本为作者最后更新于2020-06-11 18:04:43,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    根据题意,发现题目中的图,其实就是一颗树或者是一颗基环树,每个节点上有一个点对(x,y)(x,y),每次询问为给定端点,找一条直线到端点间的所有点的距离之和最小。

    设这条直线为y=kx+by=kx+b,根据点到直线公式得,我们要求i=1n(kxiyi+b)2k2+1\sum\limits_{i=1}^n \frac{(kx_i-y_i+b)^2}{k^2+1}的最小值,其中nn为路径上的点的个数。

    然后开始处理这个式子:

    $$\begin{aligned} &\sum\frac{(kx_i-y_i+b)^2}{k^2+1} \\ =&\sum\frac{(kx_i-y_i)^2+2(kx_i-y_i)b+b^2}{k^2+1} \\ =&\sum\frac{k^2x_i^2-2kx_iy_i+y_i^2+2kbx_i-2by_i+b^2}{k^2+1} \\ =&\frac{\sum k^2x_i^2-\sum 2kx_iy_i+\sum y_i^2+\sum 2kbx_i-\sum 2by_i+nb^2}{k^2+1} \\ \end{aligned} $$

    发现对于直线y=kx+by=kx+bkkbb的取值是互不影响的,所以可以先对bb进行处理,使其取到能让式子达到最小值的值,然后代入原式,再求kk能让式子达到最小值的值。

    先对含有bb的项进行分类,得:

    $$\frac{nb^2+\sum (2kx_i-2y_i)b+\sum (k^2x_i^2-2kx_iy_i+y_i^2)}{k^2+1} $$

    考虑bb作为变量,发现其为开口向上的二次函数,当$b= \frac{\sum (2y_i-2kx_i)}{2n}=\frac{\sum y_i}{n}-\frac{\sum kx_i}{n}$,发现xin\frac{\sum x_i}{n}的意义就是对于所有xix_i的平均值,所以设$\bar x=\frac{\sum x_i}{n},\bar y=\frac{\sum y_i}{n}$,将b=yˉkxˉb=\bar y-k \bar x代入原式,得:

    $$\frac{\sum k^2x_i^2-\sum 2kx_iy_i+\sum y_i^2+\sum 2k(\bar y-k \bar x)x_i-\sum 2(\bar y-k \bar x)y_i+n(\bar y-k \bar x)^2}{k^2+1} \\ $$

    然后像上面一样,对含有kk的项进行分类,得:

    $$\frac{\sum (x_i^2-2x_i \bar x+ \bar x^2)k^2+\sum(-2x_iy_i+2x_i \bar y+2 \bar x y_i -2 \bar x \bar y)k+\sum (y_i^2-2y_i \bar y+ \bar y^2)}{k^2+1} $$

    为方便接下来的处理,设出kk的系数:

    $$\begin{aligned} &A=\sum (x_i^2-2x_i \bar x+ \bar x^2)=\sum x_i^2-\frac{(\sum x_i)^2}{n}\\ &B=\sum(-2x_iy_i+2x_i \bar y+2 \bar x y_i -2 \bar x \bar y)=-2\sum x_iy_i+\frac{2\sum x_i\sum y_i}{n}\\ &C=\sum (y_i^2-2y_i \bar y+ \bar y^2)=\sum y_i^2-\frac{(\sum y_i)^2}{n}\\ \end{aligned} $$

    设原式的值为SS,得:

    $$\begin{aligned} &S=\frac{Ak^2+Bk+C}{k^2+1}\\ &Sk^2+S=Ak^2+Bk+C\\ &(A-S)k^2+Bk+C-S=0\\ \end{aligned} $$

    发现得到一个关于kk的一元二次方程,因为kk一定会有合法解,所以得:

    $$\begin{aligned} \Delta&=B^2-4(A-S)(C-S) \geqslant 0\\ &=B^2-4AC+4AS+4CS-4S^2 \geqslant 0\\ &=-4S^2+4(A+C)S+B^2-4AC \geqslant 0\\ \end{aligned} $$

    发现得到一个关于SS的一元二次不等式,将其看作开口向下的二次函数,得其值必须大于等于00,所以SS的最小取值即为其方程的较小根,得:

    $$\begin{aligned} &S=\frac{A+C-\sqrt{A^2+B^2+C^2-2AC}}{2}\\ &A=\sum x_i^2-\frac{(\sum x_i)^2}{n}\\ &B=-2\sum x_iy_i+\frac{2\sum x_i\sum y_i}{n}\\ &C=\sum y_i^2-\frac{(\sum y_i)^2}{n}\\ \end{aligned} $$

    然后维护每个点到根节点$\sum x_i,\sum y_i,\sum x_i^2,\sum y_i^2,\sum x_iy_i,n$的和,然后就可以通过树上差分求解了。对于基环树的情况,若两点在一个子树内,就直接树上差分,若两点不在一个子树内,需要经过环,则从经过环的两种方式得出的两个值取较小值作为答案。

    code:code:

    #include<bits/stdc++.h>
    #define maxn 400010
    using namespace std;
    template<typename T> inline void read(T &x)
    {
        x=0;char c=getchar();bool flag=false;
        while(!isdigit(c)){if(c=='-')flag=true;c=getchar();}
        while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
        if(flag)x=-x;
    }
    int n,m,q,tot;
    int de[maxn],f[maxn][25],fa[maxn],a[maxn],b[maxn];
    int cir[maxn],bel[maxn],pos[maxn];
    bool flag;
    bool tag[maxn];
    struct edge
    {
        int to,nxt;
    }e[maxn];
    int head[maxn],edge_cnt;
    void add(int from,int to)
    {
        e[++edge_cnt]=(edge){to,head[from]};
        head[from]=edge_cnt;
    }
    struct node
    {
        int x,y,xd,yd,mul,num;
        void insert(int a,int b)
        {
            x+=a,y+=b,xd+=a*a,yd+=b*b,mul+=a*b,num++;
        }
        double calc()
        {
            double A=xd-(double)x*x/num,B=-2*mul+2*(double)x*y/num,C=yd-(double)y*y/num;
            return (A+C-sqrt(A*A+B*B+C*C-2*A*C))/2;
        }
    }s[maxn],sum[maxn];
    node operator +(const node &a,const node &b)
    {
        return (node){a.x+b.x,a.y+b.y,a.xd+b.xd,a.yd+b.yd,a.mul+b.mul,a.num+b.num};
    }
    node operator -(const node &a,const node &b)
    {
        return (node){a.x-b.x,a.y-b.y,a.xd-b.xd,a.yd-b.yd,a.mul-b.mul,a.num-b.num};
    }
    int find(int x)
    {
        return fa[x]==x?x:fa[x]=find(fa[x]);
    }
    void dfs(int x,int fath,int col)
    {
        f[x][0]=fath,de[x]=de[fath]+1,bel[x]=col,s[x]=s[fath],s[x].insert(a[x],b[x]);
        for(int i=1;i<=18;++i) f[x][i]=f[f[x][i-1]][i-1];
        for(int i=head[x];i;i=e[i].nxt)
        {
            int y=e[i].to;
            if(y==fath||tag[y]) continue;
            dfs(y,x,col);
        }
    }
    int lca(int x,int y)
    {
        if(de[x]<de[y]) swap(x,y);
        for(int i=18;i>=0;--i)
            if(de[f[x][i]]>=de[y])
                x=f[x][i];
        if(x==y) return x;
        for(int i=18;i>=0;--i)
            if(f[x][i]!=f[y][i])
                x=f[x][i],y=f[y][i];
        return f[x][0];
    }
    void dfs_cir(int x,int fath)
    {
        if(tag[x]&&x!=cir[1])
        {
            flag=true;
            return;
        }
        for(int i=head[x];i;i=e[i].nxt)
        {
            int y=e[i].to;
            if(y==fath) continue;
            if(tag[x]&&tag[y]) continue;
            cir[++tot]=y,dfs_cir(y,x);
            if(flag) return;
            tot--;
        }
    }
    double solve(int x,int y)
    {
        int anc=lca(x,y);
        return (s[x]+s[y]-s[anc]-s[f[anc][0]]).calc();
    }
    int main()
    {
        read(n),read(m);
        for(int i=1;i<=n;++i) read(a[i]),read(b[i]),fa[i]=i;
        for(int i=1;i<=m;++i)
        {
            int x,y;
            read(x),read(y),add(x,y),add(y,x);
            if(find(x)==find(y)) cir[++tot]=x,tag[x]=tag[y]=true;
            else fa[find(x)]=find(y);
        }
        if(m==n-1) dfs(1,0,0);
        else
        {
            dfs_cir(cir[1],0);
            for(int i=1;i<=tot;++i)
            {
                int x=cir[i];
                pos[x]=i,tag[x]=true;
                sum[i]=sum[i-1],sum[i].insert(a[x],b[x]);
            }
            for(int i=1;i<=tot;++i) dfs(cir[i],0,cir[i]);
        }
        read(q);
        while(q--)
        {
            int x,y;
            read(x),read(y);
            if(m==n-1) printf("%.6lf\n",solve(x,y));
            else
            {
                if(bel[x]==bel[y]) printf("%.6lf\n",solve(x,y));
                else
                {
                    int px=pos[bel[x]],py=pos[bel[y]];
                    node val=s[x]+s[y]-s[bel[x]]-s[bel[y]];
                    if(px>py) swap(px,py);
                    printf("%.6lf\n",min((val+sum[py]-sum[px-1]).calc(),(val+sum[tot]-sum[py-1]+sum[px]).calc()));
                }
            }
        }
        return 0;
    }
    
    • 1

    信息

    ID
    2413
    时间
    1000ms
    内存
    125MiB
    难度
    6
    标签
    递交数
    0
    已通过
    0
    上传者