#三分,分治,计算几何,prim#JZOJ 3860 地壳运动

题目

\(q\)组询问查询最小生成树,边权为\(u*k1+v*k2\)\(k1,k2\)每次询问都不同)
\(n\leq 35,m\leq 25000,q\leq 200000\)


分析

\(\text{Prim}\)爽赚60
按照题解的旨意,可以建立一个下凸壳,边界是\((1,0),(0,1)\)这是系数,然后要用这个系数跑最小生成树得到\((x=\sum u,y=\sum v)\)
要找到一个最远的点使得它在凸壳上,然而我太菜了不知道怎样推出来\((abs(r.y-l.y),abs(r.x-l.x))\)
然后不断分治下去,既然是凸壳,那么任意三点不能在同一条直线上,也就是斜率相等就没有了,既然是一个下凸壳,就是一个单谷函数可以三分求解


代码

#include <cstdio>
#include <cctype>
#include <cmath>
#define rr register
using namespace std;
struct Sline{double k,b,x,y;}sl[371],head,tail; bool v[37];
int cost[37][37],X[25011],n,m,Q,Y[25011],U[25011],V[25011],Min[37],tot;
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
inline double min(double a,double b){return a<b?a:b;}
inline double answ(Sline Sl,int p){return U[p]*Sl.k+V[p]*Sl.b;}
inline void prim(Sline &Sl){
    for (rr int i=1;i<=n;++i)
    for (rr int j=1;j<=n;++j) cost[i][j]=0;
    for (rr int i=1;i<=n;++i) cost[i][i]=1;
    for (rr int i=2;i<=m+1;++i){
        if (answ(Sl,i)<answ(Sl,cost[X[i]][Y[i]]))
            cost[X[i]][Y[i]]=cost[Y[i]][X[i]]=i;
    }
    for (rr int i=1;i<=n;++i) v[i]=(i==1),Min[i]=cost[1][i];
    for (rr int i=1;i<n;++i){
        rr int minp=0;
        for (rr int j=1;j<=n;++j)
        if (!v[j]&&answ(Sl,Min[minp])>answ(Sl,Min[j])) minp=j;
        Sl.x+=U[Min[minp]],Sl.y+=V[Min[minp]],v[minp]=1;
        for (rr int j=1;j<=n;++j)
        if (!v[j]&&answ(Sl,Min[j])>answ(Sl,cost[minp][j])) Min[j]=cost[minp][j];
    }
}
inline void divi(Sline l,Sline r){
    rr Sline mid=(Sline){fabs(r.y-l.y),fabs(r.x-l.x),0,0}; prim(mid);
    if (l.y==mid.y||mid.y==r.y||(l.x-mid.x)/(l.y-mid.y)==(l.x-r.x)/(l.y-r.y)) return;
    divi(l,mid),sl[++tot]=mid,divi(mid,r);
}
inline double calc(double k1,double k2,int p){return sl[p].x*k1+sl[p].y*k2;}
signed main(){
    n=iut(),m=iut(),Q=iut(),U[0]=V[0]=1e7;
    for (rr int i=2;i<=m+1;++i) X[i]=iut(),Y[i]=iut(),U[i]=iut(),V[i]=iut();
    head.k=tail.b=1,prim(head),prim(tail),sl[++tot]=head,divi(head,tail),sl[++tot]=tail;
    for (rr int i=1;i<=Q;++i){
        rr double k1,k2,ans=1e18; rr int l=1,r=tot;
        scanf("%lf%lf",&k1,&k2);
        while (l+2<r){
            rr int k=(r-l+1)/3,lmid=l+k,rmid=r-k;
            if (calc(k1,k2,lmid)<calc(k1,k2,rmid)) r=rmid; else l=lmid;
        }
        for (rr int i=l;i<=r;++i) ans=min(ans,calc(k1,k2,i));
        printf("%.3lf\n",ans);
    }
    return 0;
}
posted @ 2020-02-09 12:52  lemondinosaur  阅读(137)  评论(0编辑  收藏  举报