dtoj#4259. 越野赛车问题

题目描述:

小 $H$ 是一位优秀的越野赛车女选手。现在她准备在 $A$ 山上进行赛车训练。

$A$ 山上一共有 $n$ 个广场,编号依次为 $1$ 到 $n$ ,这些广场之间通过 $n-1$条双向车道直接或间接地连接在一起。对于每条车道$i$ ,可以用四个正整数 $u_i,v_i,l_i,r_i$描述,表示车道连接广场 $u_i$ 和 $v_i$ ,其速度承受区间为$[l_i,r_i]$,即汽车必须以不小于$l_i$ 且不大于 $r_i$ 的速度经过车道$i$

小 $H$ 计划进行 $m$ 次训练,每次她需要选择 $A$ 山上的一条简单路径,然后在这条路径上行驶。但小 $H$ 不喜欢改变速度,所以每次训练时的车速都是固定的。

现在小 $H$ 告诉你她在$m$ 次训练中计划使用的车速,请帮助她对于每次训练,找到一条合法的路径(车速在所有车道的速度承受区间的交集内),使得路径上经过的车道数最大。

算法标签:线段树,可持久化并查集,动态维护树的直径,rmq

思路:

建一棵以 $l,r$ 为下标的线段树,把每条边放到区间里,每次对于这个区间的边连到图中,启发式合并,并查集不路径压缩。用一个栈记录下每层实现了那些修改,把被修改的数记录下来,之后推出这层时复原。

对于直径的维护,每次使两个连通分量连接,可以证明直径一定是由未合并前两个联通块的直径的端点连接组成。考虑对于至多四个点枚举两两之间的路径长度,得到新联通块的直径。求 $lca$ 用 $rmq$ 提前预处理。

以下代码:

#include<bits/stdc++.h>
#define il inline
#define _(d) while(d(isdigit(ch=getchar())))
using namespace std;
const int N=7e4+5;
int top,mx[N],Ans,tot;
int d[N],id[N],rq[N<<1][20],sz[N],fa[N],p1[N],p2[N];
int n,m,head[N],ne[N<<1],to[N<<1],cnt,Lg[N<<1],res[N],gg[4];
vector<int> t[N<<2],s[N];
struct node{
    int x,y;
}g[N];
struct data{
    int x,y,len,p1,p2;
}q[N];
il int read(){
   int x,f=1;char ch;
   _(!)ch==‘-‘?f=-1:f;x=ch^48;
   _()x=(x<<1)+(x<<3)+(ch^48);
   return f*x;
}
il int Min(int x,int y){
    return d[x]<d[y]?x:y;
}
il void insert(int x,int y){
    ne[++cnt]=head[x];
    head[x]=cnt;to[cnt]=y;
}
il void ins(int x,int l,int r,int ql,int qr,int v){
    if(ql<=l&&r<=qr){t[x].push_back(v);return;}
    int mid=(l+r)>>1;
    if(ql<=mid)ins(x<<1,l,mid,ql,qr,v);
    if(mid<qr)ins(x<<1|1,mid+1,r,ql,qr,v);
}
il void dfs(int x,int fa){
    id[x]=++tot;rq[tot][0]=x;
    for(int i=head[x];i;i=ne[i]){
        if(fa==to[i])continue;
        d[to[i]]=d[x]+1;dfs(to[i],x);
        rq[++tot][0]=x;
    }
}
il int Lca(int x,int y){
    int l=id[x],r=id[y];
    if(l>r)swap(l,r);
    int k=Lg[r-l+1];
    return Min(rq[l][k],rq[r-(1<<k)+1][k]);
}
il int getfa(int x){
    return fa[x]==x?x:getfa(fa[x]);
}
il void add(int p){
    int x=g[p].x,y=g[p].y;
    int a=getfa(x),b=getfa(y);
    if(sz[a]<sz[b])swap(a,b),swap(x,y);
    q[++top]=(data){a,b,mx[a],p1[a],p2[a]};
    gg[0]=p1[a];gg[1]=p2[a];gg[2]=p1[b];gg[3]=p2[b];
    int maxn=-1,f1,f2;
    for(int i=0;i<4;i++){
        for(int j=i+1;j<4;j++){
            int lca=Lca(gg[i],gg[j]);
            int len=d[gg[i]]+d[gg[j]]-2*d[lca];
            if(len>maxn)maxn=len,f1=gg[i],f2=gg[j];
        }
    }
    p1[a]=f1;p2[a]=f2;mx[a]=maxn;fa[b]=a;sz[a]+=sz[b];
    Ans=max(Ans,maxn);
}
il void del(int p){
    while(top>p){
        int a=q[top].x,b=q[top].y;
        sz[a]-=sz[b];
        fa[b]=b;mx[a]=q[top].len;
        p1[a]=q[top].p1;p2[a]=q[top].p2;
        top--;
    }
}
il void solve(int x,int l,int r){
    int tmp=top,ret=Ans;
    for(int i=0;i<t[x].size();i++)add(t[x][i]);
    if(l==r){
        for(int i=0;i<s[l].size();i++)res[s[l][i]]=Ans;
        del(tmp);Ans=ret;
        return;
    }
    int mid=(l+r)>>1;
    solve(x<<1,l,mid);solve(x<<1|1,mid+1,r);
    del(tmp);Ans=ret;
}
int main()
{
    n=read();m=read();
    for(int i=1;i<n;i++){
        int x=read(),y=read(),l=read(),r=read();
        insert(x,y);insert(y,x);g[i]=(node){x,y};
        ins(1,1,n,l,r,i);
    }
    for(int i=1;i<=m;i++){
        int x=read();s[x].push_back(i);
    }
    d[1]=1;dfs(1,0);
    for(int i=2;i<=tot;i++)Lg[i]=Lg[i>>1]+1;
    for(int j=1;j<=Lg[tot];j++)for(int i=1;i+(1<<j)-1<=tot;i++)
        rq[i][j]=Min(rq[i][j-1],rq[i+(1<<(j-1))][j-1]);
    for(int i=1;i<=n;i++)sz[i]=1,fa[i]=p1[i]=p2[i]=i;
    solve(1,1,n);
    for(int i=1;i<=m;i++)printf("%d\n",res[i]);
    return 0;
}

原文地址:https://www.cnblogs.com/Jessie-/p/10562355.html

时间: 2024-08-06 00:57:56

dtoj#4259. 越野赛车问题的相关文章

「校内训练 2019-04-23」越野赛车问题 动态dp+树的直径

题目传送门 http://192.168.21.187/problem/1236 http://47.100.137.146/problem/1236 题解 题目中要求的显然是那个状态下的直径嘛. 所以这道题有一个非常简单的做法--线段树分治. 直接把每一条边按照 \(l, r\) 的区间放到线段树上进行分治,遍历的时候用并查集维护直径就可以了. 时间复杂度为 \(O(n\log^2n)\). 很早以前就写了这个算法,代码附在了最后,不多讲了. 但是这道题还有一个方法--动态 DP. 线段树分治

《构造之法》第四章

通过阅读<构造之法>第四章,我知道了程序员写的代码要规范,代码虽然是给机器看的,让机器来运行,但是更要给人看,代码是人写的,都会有各种各样的缺陷,必然要去修复.改进,若代码不规范,带来的影响是比较严重的,会让人感到烦躁,看不下去了.或者因为看懂代码的缺陷在哪里而花费了大量的时间.在代码上要4个空格来缩进.为了调试起来方便,需要断行,每个“{”和“}”都独占一行,一行代码不要定义多个变量,由多个单词组成的变量名需要有大小写,其中每个单词的开头第一个字母大写,不要多余的注释. 代码复审是有必要的,

全球首辆3D打印汽车亮相 零部件打印只需44小时

9 月 16 日,3D 打印和电动汽车,均是科技行业时下的热门领域,这两个领域有了交集.日前,全世界第一辆零部件均用 3D 打印制造而成的汽车,在美国芝加哥揭开了面纱. 这辆电动车名为"Strati",由位于美国亚利桑那州凤凰城的"Local 汽车公司"制造,上周,美国芝加哥举行了"国际制造技术展",这辆电动车在展会上亮相. 这辆依靠 3D 打印制造而成的电动车,最高时速可达每小时 64 公里,一次充电最远可以行驶 192 公里. 在国际制造技术

结对学习感想及创意照

结对学习,讲究的是一起学习.现在很多人结对不是为了一起学习,而是为了傍大佬,抱大腿想要沾沾光蒙混过关.所以结对应该是两个旗鼓相当,功底差不多的人,拥有共同的爱好,都有相近的学习习惯,在一起一起学习才有讨论的意思,否则一方比较强就会有一方产生依赖心理,久而久之就是依附他们不是结对学习而是吃软饭. 如邹欣老师博客所说,结对学习在生活中很多这样的例子 : 越野赛车(驾驶,领航员) 驾驶飞机(驾驶,副驾驶) 战斗机的编组(长机,僚机) 这些任务都有共同点:在高速度中完成任务,任务有较高的技术要求,任务失

unity3d 赛车游戏——复位点检测

一直没有时间写博客 昨天我的CarWaypoints插件也告一段落了 今年没回家,过年就我一个人 挺无聊的,那就休息一天写几篇博客吧 我的代码可能很少,但是思路很重要 希望不懂的朋友别只copy代码 赛车游戏的话赛车难免会冲出跑道.掉入水坑.卡在障碍物上....等情况 那么问题来了,遇到这些情况怎么办呢? 玩家玩得好好的,难道就因为遇到这些情况要退出游戏重新进入吗? 那当然是不现实的,要是我的话果断卸载游戏 还要骂一句做游戏的人是脑残啊 我想你不希望玩家骂你是脑残吧,哈哈哈 新技能,赶快GET起

赛车比赛(洛谷U4566)

题目背景 kkk在赛车~ 题目描述 现在有N辆赛车行驶在一条直线跑道(你可以认为跑道无限长)上.它们各自以某种速度匀速前进,如果有两辆车A车和B车,A车在B车的后面,且A车的速度大于B车的速度,那么经过一定的时间后,A车必定会超过B车,这称为一次超车.求超车总数.道路起点的位置为0,没有两辆车的初始位置相同. 输入输出格式 输入格式: 第一行,一个数n,车辆的总数. 第二行~第n+1行,为n辆车的信息,每行有两个正整数x,y.X为起始位置,y为速度.0<x,y<=1000000 输出格式: 超

HTML5物理游戏开发 - 越野山地自行车(三)粉碎自行车

自上一章发布到现在已时隔四月,实在对不住大家,让大家久等了~话说不是我不关注我的博客,而是事情一多起来写博客的时间就少了.待到今日有空了,回头看了看自己以前写的文章,猛得发现已经四个月不曾写文章了,便只得叫声:"苦也~",我害怕本系列文章会拖得更久,于是立刻提笔,也好为本系列文章留个凤尾. 首先,大家来温习一下前面两篇里的内容吧: HTML5物理游戏开发 - 越野山地自行车(二)创建一辆可操控的自行车 http://blog.csdn.net/yorhomwang/article/de

unity3d 赛车游戏——复位点检测优化、反向检测、圈数检测、赛道长度计算

接着上一篇文章说 因为代码简短且思路简单 所以我就把这几个功能汇总为一篇文章 因为我之前就是做游戏外挂的 经过验证核实,**飞车的复位点检测.圈数检测就是以下的方法实现的 至于反向检测和赛道长度计算,没去深入研究,不过应该也八九不离十 在告诉大家个小秘密: **飞车的复位点检测和圈数检测利用以下文章中的代码思路可以做出外挂 感兴趣的可以试试!我只是技术交流,不是传播外挂,别打我 复位点检测优化: 首先感谢 @太粗难进 他的原话: “不过 你知道 高架桥么?就是 如果大的轮船经过 会 把 桥 中间

BZOJ3190[JLOI2013]赛车

Description 这里有一辆赛车比赛正在进行,赛场上一共有N辆车,分别称为个g1,g2--gn.赛道是一条无限长的直线.最初,gi位于距离起跑线前进ki的位置.比赛开始后,车辆gi将会以vi单位每秒的恒定速度行驶.在这个比赛过程中,如果一辆赛车曾经处于领跑位置的话(即没有其他的赛车跑在他的前面),这辆赛车最后就可以得奖,而且比赛过程中不用担心相撞的问题.现在给出所有赛车的起始位置和速度,你的任务就是算出那些赛车将会得奖. Input 第一行有一个正整数N表示赛车的个数. 接下来一行给出N个