Dash Speed

Dash Speed

Time limit: 1 seconds
Memory limit: 128 megabytes

比特山是比特镇的飙车圣地。在比特山上一共有 n 个广场,编号依次为 1 到 n,这些广场之间通过 n1 条双向车道直接或间接地连接在一起,形成了一棵树的结构。因为每条车道的修建时间以及建筑材料都不尽相同,所以可以用两个数字 li,ri 量化地表示一条车道的承受区间,只有当汽车以不小于 li 且不大于 ri 的速度经过这条车道时,才不会对路面造成伤害。

Byteasar 最近新买了一辆跑车,他想在比特山飙一次车。Byteasar 计划选择两个不同的点 S, T,然后在它们树上的最短路径上行驶,且不对上面任意一条车道造成伤害。

Byteasar 不喜欢改变速度,所以他会告诉你他的车速。为了挑选出最合适的车速,Byteasar 一共会向你询问 m 次。请帮助他找到一条合法的道路,使得路径上经过的车道数尽可能多。

输入格式

第一行包含两个正整数 n, m,表示广场的总数和询问的总数。
接下来 n1 行,每行四个正整数 ui,vi,li,ri ,表示一条连接 ui vi 的双向车道,且承受区间为 [li,ri]

接下来 m 行,每行一个正整数 qi ,分别表示每个询问的车速。

输出格式
输出 m 行,每行一个整数,其中第 i 行输出车速为 qi 时的最长路径的长度,如果找不到合法的路径则输出 0。

样例输入输出
speed.in

5 3
3 2 2 4
1 5 2 5
4 5 2 2
1 2 3 5
1
2
3

speed.out

0
2
3

样例解释

当车速为 1 时,不存在合法的路径。
当车速为 2 时,可以选择 1-5-4 这条路径,长度为 2。
当车速为 3 时,可以选择 3-2-1-5 这条路径,长度为 3。

数据范围
对于 100% 的数据, 1ui,vi,qin,1lirin

测试点编号nm约定
1 =5 =5
2 =20 =20
3 =50000 =50000 li=1 ,且 ui=i,vi=i+1
4 =70000 =70000 li=1 ,且 ui=i,vi=i+1
5 =50000 =50000 ui=i , vi=i+1
6 =70000 =70000 ui=i , vi=i+1
7 = 50000 =50000 =50000 li=1
8 =70000 =70000 li=1
9 =50000 =50000
10 =70000 =70000

本题考查了许多知识点,比较复杂。

首先我们能看出这是一道求最小生成树的题目,写个裸的最小生成树就可以过前几个点了,对于后面 3 个点,我们发现在线算法没有充分利用计算过的信息,所以我们自然而然地考虑线段树。

我们在建树的时候,对于每个线段树上的节点,我们取所有整个区间都满足其条件的边加入节点的边集。(我的代码中 struct TE 存的是每个节点上的边集,而线段树的本体并没有存下来,主要是为了写得方便)

建好线段树之后,我们开始分治操作。本题使用的是 CDQ分治。

cdq分治是一种特别的分治方法,它由cdq神牛于09国家集训队作业中首次提出,因此得名。

首先,cdq分治属于分治的一种。它一般只能处理非强制在线的问题,除此之外这个算法作为某些复杂算法的替代品几乎是没有缺点的。

考虑这样一个问题,有若干询问和若干修改,要求在 O(nlogn) 时间复杂度内回答所有的询问。(下把询问与修改统称为操作)
我们把[l,r]代表当前处理的操作的区间,即处理第l个到第r个操作,先找到区间的中间m=(l+r)/2

  1. 然后对于前一半[l,m]我们先递归解决。
  2. 对于所有在[l,m]内的修改操作,枚举处理它对于[m,r]内的所有操作『影响』。
  3. 之后递归处理[m,r]这一区间。

复杂度分析:分治共有log(n)层,那么要求每一层都在线性的时间复杂度内完成,才能保证总时间复杂度是O(nlogn)
(引自bill125的博客

我们注意到,最后求的答案是一个长度唯一的区间(即一个确定的速度)的答案,所以我们从上往下更新最长链。一个节点的父亲的边集一定包含于此节点的边集,所以我们从上到下走的时候其实是一个加边的过程,是做最小生成树的过程。不过我们发现一个问题:如果按照一般的 Kruskal 做法,使用路径压缩优化的并查集,对,就长这样:

所以我们使用另一种方式:按秩合并,这样就可以保证在回溯的时候能够快速地撤销操作并且可以在图中找出最长链。

我们就按此方式分治,到达线段树的叶子节点的时候,此时的答案就可能是我们要求的了,于是存起来。对每个询问即可在 O(1) 的复杂度内回答。


#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;

const int MAXN=7e4+5;

inline int read();
inline void swap(int & x,int &y){int t=x;x=y;y=t;}

int n,m;

//G on CFS
struct E{int next,to;} e[MAXN<<1];int ecnt,G[MAXN];
void addEdgeG(int u,int v)
{
    e[++ecnt]=(E){G[u],v};G[u]=ecnt;
    e[++ecnt]=(E){G[v],u};G[v]=ecnt;
}

//SEGT on CFS
struct TE{int next,u,v;} t[MAXN<<2];int tcnt,T[MAXN];
void addEdgeT(int o,int u,int v)
{t[++tcnt]=(TE){G[o],u,v};T[o]=tcnt;}
int ql,qr,qu,qv;
void buildT(int o,int l,int r)
{
    if(ql<=l&&r<=qr) {addEdgeT(o,qu,qv);return;}
    int mid=(l+r)>>1;
    if(ql<=mid) buildT(o<<1,l,mid);
    if(qr>mid) buildT(o<<1+1,mid+1,r);
}

//cal some val about deepth; generate dfs array
int dfa[MAXN],dcnt,firPos[MAXN],dpt[MAXN];
//dfa: dfs array; firPos: firPosst pos of u in dfa; dpt: deepth.
void dfs(int u,int f)
{
    dfa[++dcnt]=u;
    dpt[u]=dpt[f]+1;
    firPos[u]=dcnt;
    for(int i=G[u];i;i=e[i].next)
    {
        int v=e[i].to;
        if(v!=f)
        {
            dfs(v,u);
            dfa[++dcnt]=u;
        }
    }
}

//ST->RMQ->LCA->Dis
int st[MAXN][20];
inline void initST()
{
    int i,j;
    for(i=1;i<=dcnt;i++) st[i][0]=i;
    for(i=1;(1<<i)<=n;i++)
        for(j=1;j+(1<<i)-1<=dcnt;j++)
            st[j][i]=max(st[j][i-1],st[j+(1<<(i-1))][i-1]);
}
inline int calRMQ(int l,int r)
{
    int i=log(r-i+1)/log(2);
    return max(st[l][i],st[r-(1<<i)+1][i]);     
}
inline int calDis(int x,int y)
{
    int xPos=firPos[x],yPos=firPos[y],lca;
    if(xPos>yPos) lca=calRMQ(firPos[y],firPos[x]);
    else lca=calRMQ(firPos[x],firPos[y]);
    return dpt[x]+dpt[y]-(dpt[lca]<<1);
}

//UnoinFind
int ans[MAXN],rank[MAXN],p[MAXN][2];//p[i]:start point & end point of the maxlen chain of i.
struct N{int yu,yv,u,v,sz;} c[MAXN];
int fa[MAXN];
int calAnc(int x)
{return fa[x]?calAnc(fa[x]):x;}

int calc(int x)
{
    int i,j;
    int uAnc=calAnc(t[x].u),vAnc=calAnc(t[x].v);
    int o[2],uDis,vDis,dis;
    uDis=calDis(p[uAnc][0],p[uAnc][1]);
    vDis=calDis(p[vAnc][0],p[vAnc][1]);
    if(uDis>=vDis) dis=uDis,o[0]=p[uAnc][0],o[1]=p[uAnc][1];
    else dis=vDis,o[0]=p[vAnc][0],o[1]=p[uAnc][1];

    int rDis;
    for(i=0;i<=1;i++)
        for(j=0;j<=1;j++)
        {
            rDis=calDis(p[uAnc][i],p[vAnc][j]);
            if(rDis>dis)
            {
                dis=rDis;
                o[0]=p[uAnc][i],o[1]=p[vAnc][j];
            }
        }

    if(rank[uAnc]>rank[vAnc])
    {
        c[x].u=vAnc,c[x].v=uAnc;
        c[x].yu=p[uAnc][0],c[x].yv=p[uAnc][1];
        p[uAnc][0]=o[0],p[uAnc][1]=o[1];
        fa[vAnc]=uAnc;
    }else
    {
        c[x].u=uAnc,c[x].v=vAnc;
        c[x].yu=p[vAnc][0],c[x].yv=p[vAnc][1];
        p[vAnc][0]=o[0],p[vAnc][1]=o[1];
        fa[uAnc]=vAnc;
        if(rank[uAnc]==rank[vAnc]) rank[vAnc]++,c[x].sz=1;
    }
    return calDis(o[0],o[1]);
}

int q[MAXN<<1],qcnt;
void clean(int x)
{
    int i;
    for(int i=T[x];i;i=t[i].next) q[++qcnt]=i;
    for(i=1;i<=qcnt;i++)
    {
        p[c[q[i]].v][0]=c[q[i]].yu;
        p[c[q[i]].v][1]=c[q[i]].yv;
        fa[c[q[i]].v]=0;
        rank[c[q[i]].v]-=c[q[i]].sz;
    }qcnt=0;
}
//Divide&Conquer
void DC(int o,int l,int r,int maxv)
{
    for(int i=T[o];i;i=t[i].next)//attention:t[i]
        maxv=max(calc(i),maxv);
    if(l==r) {ans[l]=maxv;clean(o);return;}
    int mid=(l+r)>>1;
    DC(o<<1,l,mid,maxv);
    DC(o<<1+1,mid+1,r,maxv);
    clean(o);
}

int main()
{
    freopen("speed.in","r",stdin);
    freopen("speed.out","w",stdout);
    int i;
    n=read();m=read();
    for(i=1;i<n;i++)
    {
        int u=read(),v=read(),l=read(),r=read();
        addEdgeG(u,v);
        qu=u,qv=v,ql=l,qr=r;
        buildT(1,1,n);
    }
    dfs(1,0);
    initST();
    for(i=1;i<=n;i++) p[i][0]=p[i][1]=i;
    DC(1,1,n,0);
    for(i=1;i<=m;i++) printf("%d\n",ans[read()]);
}

inline int read()
{
    int res=0;int flag;char c=getchar();
    while((c<'0'||c>'9')&&c!='-') c=getchar();
    if(c=='-') flag=-1;
    else res=res*10+c-'0';
    while(c=getchar()&&'0'<=c&&c<='9')
        res=res*10+c-'0';
    return flag*res;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值