BZOJ 2402 陶陶的难题II (树链剖分、线段树、凸包、分数规划)

毒瘤,毒瘤,毒瘤……

\(30000\)这个数据范围,看上去就是要搞事的啊。。。

题目链接: https://www.lydsy.com/JudgeOnline/problem.php?id=2402

题解:

首先化式子: 假设二分的答案为\(mid\)\(\frac{y_i+q_j}{x_i+p_j}\ge mid, y_i+q_j\ge mid(x_i+p_j), y_i-mid\times x_i+q_j-mid\times p_j\ge 0\)

那么我们实际上就是要找一个\(i\)使得\(-x_i\times mid+y_i\)最大,同理找一个\(j\)使得\(-p_j\times mid+q_j\)最大,这两个任务完全一样,现在就讨论前者

为了方便(强迫症)让我们把它变成\(-a_ix+b_i\)的形式

仿照斜率优化的思路,如果\(a_i<a_j\),那么\(i\)不劣于\(j\)当且仅当\(-a_ix+b_i\ge -a_jx+b_j, x\ge \frac{b_j-b_i}{a_j-a_i}\).

那么假设有并排着的三个点\(i,j,k,a_i<a_j<a_k\), 则\(i\)不劣于\(j\)当且仅当\(x\ge \frac{b_j-b_i}{a_j-a_i}\), \(k\)不劣于\(j\)当且仅当\(x\le \frac{b_k-b_j}{a_k-a_j}\), 如果\(\frac{b_j-b_i}{a_j-a_i}\le \frac{b_k-b_j}{a_k-a_j}\), 那么对于任何\(x\)上面两个条件至少有一个成立,那么\(j\)就是无用的。也就是说有用的点一定满足斜率递减,在上凸壳的左半边上。

这题没有修改操作,那么我们可以用线段树求出每个子区间内的凸壳,然后对于每个询问,先分数规划二分答案,然后求树链剖分划分成的每一段区间的最大值,这个可以通过在线段树每个子区间的凸壳上二分得到。

时间复杂度\(O(n\log^4n)\)

(这可能是我最近写的几道题唯一一道跑进BZOJ前一半的?23333)

代码
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cassert>
#include<vector>
using namespace std;

const int N = 3e4;
const double EPS = 1e-8;
const double INF = 1e6;
int dcmp(double x) {return x<-EPS ? -1 : (x>EPS ? 1 : 0);}
struct Point
{
    double x,y;
    Point() {}
    Point(double _x,double _y) {x = _x,y = _y;}
    bool operator <(const Point &arg) const {return dcmp(x-arg.x)<0 || (dcmp(x-arg.x)==0 && dcmp(y-arg.y)<0);}
};
struct Edge
{
    int v,w,nxt;
} e[(N<<1)+3];
int fe[N+3];
int fa[N+3];
int dfn[N+3];
int idfn[N+3];
int sz[N+3];
int tpn[N+3];
int dep[N+3];
int hvs[N+3];
double a1[N+3],b1[N+3],a2[N+3],b2[N+3];
int n,q,en,cnt;

struct SegmentTree
{
    vector<Point> sgt[(N<<2)+3];
    void CHpush(int id,Point x)
    {
        int j = sgt[id].size()-1;
        while(j>0 && dcmp((sgt[id][j].y-sgt[id][j-1].y)*(x.x-sgt[id][j].x)-(sgt[id][j].x-sgt[id][j-1].x)*(x.y-sgt[id][j].y))<=0)
        {
            sgt[id].pop_back();
            j--;
        }
        sgt[id].push_back(x);
    }
    void build(int rtn,int le,int ri,double a[],double b[])
    {
        if(le==ri)
        {
            sgt[rtn].push_back(Point(a[idfn[le]],b[idfn[le]]));
            return;
        }
        int mid = (le+ri)>>1;
        build(rtn<<1,le,mid,a,b);
        build(rtn<<1|1,mid+1,ri,a,b);
        int i = 0,j = 0;
        while(i<sgt[rtn<<1].size() && j<sgt[rtn<<1|1].size())
        {
            if(sgt[rtn<<1][i]<sgt[rtn<<1|1][j]) {CHpush(rtn,sgt[rtn<<1][i]); i++;}
            else {CHpush(rtn,sgt[rtn<<1|1][j]); j++;}
        }
        while(i<sgt[rtn<<1].size()) {CHpush(rtn,sgt[rtn<<1][i]); i++;}
        while(j<sgt[rtn<<1|1].size()) {CHpush(rtn,sgt[rtn<<1|1][j]); j++;}
    }
    double calc(int rtn,double x)
    {
        int left = 0,right = sgt[rtn].size()-1;
        while(left<right)
        {
            int mid = (left+right+1)>>1;
            if(mid==0) {break;}
            if(dcmp(x*(sgt[rtn][mid].x-sgt[rtn][mid-1].x)-(sgt[rtn][mid].y-sgt[rtn][mid-1].y))<=0) {left = mid;}
            else {right = mid-1;}
        }
        double ret = -sgt[rtn][left].x*x+sgt[rtn][left].y;
        return ret;
    }
    double query(int rtn,int le,int ri,int lb,int rb,double x)
    {
        if(le>=lb && ri<=rb) {return calc(rtn,x);}
        int mid = (le+ri)>>1; double ret = -INF;
        if(lb<=mid) {ret = max(ret,query(rtn<<1,le,mid,lb,rb,x));}
        if(rb>mid) {ret = max(ret,query(rtn<<1|1,mid+1,ri,lb,rb,x));}
        return ret;
    }
} sgt1,sgt2;

void addedge(int u,int v)
{
    en++; e[en].v = v;
    e[en].nxt = fe[u]; fe[u] = en;
}

void dfs1(int u)
{
    sz[u] = 1; hvs[u] = 0;
    for(int i=fe[u]; i; i=e[i].nxt)
    {
        int v = e[i].v;
        if(v==fa[u]) continue;
        fa[v] = u;
        dep[v] = dep[u]+1;
        dfs1(v);
        sz[u] += sz[v];
        if(sz[v]>sz[hvs[u]]) {hvs[u] = v;}
    }
}

void dfs2(int u)
{
    cnt++; dfn[u] = cnt; idfn[cnt] = u;
    if(hvs[u]) {tpn[hvs[u]] = tpn[u]; dfs2(hvs[u]);}
    for(int i=fe[u]; i; i=e[i].nxt)
    {
        int v = e[i].v;
        if(v==hvs[u] || v==fa[u]) continue;
        tpn[v] = v;
        dfs2(v);
    }
}

bool query(int u,int v,double x)
{
    double ret1 = -INF,ret2 = -INF;
    while(tpn[u]!=tpn[v])
    {
        if(dep[fa[tpn[u]]]>dep[fa[tpn[v]]])
        {
            double tmp = sgt1.query(1,1,n,dfn[tpn[u]],dfn[u],x);
            ret1 = max(ret1,tmp);
            tmp = sgt2.query(1,1,n,dfn[tpn[u]],dfn[u],x);
            ret2 = max(ret2,tmp);
            u = fa[tpn[u]];
        }
        else
        {
            double tmp = sgt1.query(1,1,n,dfn[tpn[v]],dfn[v],x);
            ret1 = max(ret1,tmp);
            tmp = sgt2.query(1,1,n,dfn[tpn[v]],dfn[v],x);
            ret2 = max(ret2,tmp);
            v = fa[tpn[v]];
        }
    }
    if(dep[u]>dep[v]) swap(u,v);
    double tmp = sgt1.query(1,1,n,dfn[u],dfn[v],x);
    ret1 = max(ret1,tmp);
    tmp = sgt2.query(1,1,n,dfn[u],dfn[v],x);
    ret2 = max(ret2,tmp);
    if(dcmp(ret1+ret2)>0) return true;
    return false;
}

int main()
{
    scanf("%d",&n);
    for(int i=1; i<=n; i++) scanf("%lf",&a1[i]);
    for(int i=1; i<=n; i++) scanf("%lf",&b1[i]);
    for(int i=1; i<=n; i++) scanf("%lf",&a2[i]);
    for(int i=1; i<=n; i++) scanf("%lf",&b2[i]);
    for(int i=1; i<n; i++)
    {
        int x,y; scanf("%d%d",&x,&y);
        addedge(x,y); addedge(y,x);
    }
    dep[1] = 1; dfs1(1);
    tpn[1] = 1; dfs2(1);
    sgt1.build(1,1,n,a1,b1);
    sgt2.build(1,1,n,a2,b2);
    scanf("%d",&q);
    for(int i=1; i<=q; i++)
    {
        int x,y; scanf("%d%d",&x,&y);
        double left = 0.0,right = 1e5;
        while(right-left>5e-5)
        {
            double mid = (left+right)*0.5;
            bool ans = query(x,y,mid);
            if(ans) {left = mid;}
            else {right = mid;}
        }
        printf("%lf\n",left);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值