Luogu P4768 [NOI2018] 归程
蒟蒻的第一篇题解QAQ 不太会写,人菜瘾大
第一次写Kruskal重构树,边WA边Debug
题目传送门
题意
对于一张有
每天的出发点有一辆车,可以开过高于当天水位线的边,对于海拔低于或等于当天水位线的边,只能步行通过,并且车无法通过,这意味着经过此边后的所有边都只能步行通过。
每次询问对于当天水位线为
思路
我们可以把这张图大致分为两部分,一部分是可以开车到达的点,这些点不需要步行;另一部分就是需要步行的路了。
我们的目标是需要步行的路最短,也就是要把可以开车到达的那一部分点给找出来,那部分点中距离1号点最近的点即为答案了。
对于每个点距离1号点的距离,我们可以一次Dijkstra求出。
而对于哪些点可以由汽车到达,也就是哪些点可以由都在当天水位线上的边到达的,我们可以用Kruskal重构树求得。
首先我们把每条边按照海拔由高到低排序,然后跑Kruskal重构树,此时构建的树去求两点lca即为两点间所有可行的路里最低的边的最大值。
对于每次询问,如果出发点在重构树中的父节点权值小于等于水位线,则意味着出发点无法驱车前往任意点,直接返回出发点到1节点的步行距离即可。
否则,找到重构树中深度最小且海拔大于水位线的节点,依据重构树是小根堆的性质,该节点的子树内的所有节点海拔都比该节点大,即返回该节点及其子树内所有节点到达1号节点的最小距离。对于找节点的操作,可以用树上倍增解决。对于找子树中到1号点最小距离的操作,可以用dfs预处理一遍。
代码
Dijkstra求最短路
class dij{
public:
long long d;
int u;
bool operator > (const dij& a)const{return d>a.d;}
};
void dijkstra()
{
d[1]=0;
priority_queue<dij,vector<dij>,greater<dij> > Q;
Q.push({0, 1});
while(!Q.empty())
{
int now=Q.top().u;
Q.pop();
if(vis[now])continue;
vis[now]=1;
for(int i=head_e[now]; i; i=E[i].next)
{
int v=E[i].v;
int w=E[i].w;
if(d[v]>d[now]+w)
{
d[v]=d[now]+w;
Q.push({d[v], v});
}
}
}
}
Kruskal重构树
void kruskal()
{
int tmp=0;
for(int i=1;i<=m;i++)
{
int x=V[i].u,y=V[i].v,h=V[i].h;
int tx=get(x),ty=get(y);//并查集操作
if(tx==ty)continue;
tmp++;
int mubiao=++tree_p;
tree_w[mubiao]=h;
add_oe(tx,mubiao);//第一次写kruskal重构树,一堆加边操作
add_oe(mubiao,tx);
add_oe(ty,mubiao);
add_oe(mubiao,ty);
fa[tx]=mubiao;//并查集合并
fa[ty]=mubiao;
if(!rely[x])rely[x]=mubiao;//记录重构树中其父节点
if(!rely[y])rely[y]=mubiao;
if(tmp==n-1)break;
}
}
倍增预处理
void dfs_bz(int u,int from)
{
faa[u][0]=from;//无倍增时祖先为其父节点
deepth[u]=deepth[from]+1;//深度+1
for(int i=1;i<=lg[deepth[u]];i++)
{
faa[u][i]=faa[faa[u][i-1]][i-1];//对其父节点的倍增预处理
}
for(int i=head_oe[u]; i; i=OE[i].next)
{
int to=OE[i].v;
if(to<=n)continue;
if(to==from)continue;
dfs_bz(to,u);
}
}
dfs预处理答案
void dfs_ans(int u,int from)
{
for(int i=head_oe[u]; i; i=OE[i].next)
{
int v=OE[i].v;
if(v==from)continue;
if(v<=n)
{
ans[u]=min(ans[u],d[v]);//如果是实点,则更新本虚点的权值
}
else
{
dfs_ans(v,u);//如果是虚点,则先继续深搜
ans[u]=min(ans[u],ans[v]);//然后用其更新本点权值
}
}
}
寻找深度最浅且海拔大于P的祖先节点
int find_u()
{
int tmp=rely[st];//tmp为实点在重构树中的父虚点
while(!(tree_w[faa[tmp][0]]<=P&&tree_w[tmp]>P))//如果本节点不是深度最小的,且海拔大于P的节点
{
for(int i=lg[deepth[tmp]-1];i>=0;i--)
{
if(tree_w[faa[tmp][i]]>P)//倍增寻找海拔大于P的祖先节点
{
tmp=faa[tmp][i];
break;
}
}
if(tmp==tree_p)break;//如果已经是根节点,则break
}
return tmp;
}
完整代码
点击查看代码
/*
* ycccc319
*/
#include <bits/stdc++.h>
const int MAXN=400010;
using namespace std;
int n,m,q,K,S,st,P;
class node{
public:
int u,v,h;
}V[MAXN];//kruskal最小生成树用
class edge{
public:
int v,w,next;
}E[MAXN<<1];//最短路用
class oedge{
public:
int v,next;
}OE[MAXN<<1];//kruskal重建树边
class dij{
public:
long long d;
int u;
bool operator > (const dij& a)const{return d>a.d;}
};
long long d[MAXN],lastans=0,ans[MAXN];
int head_e[MAXN],head_oe[MAXN],cnt_e,cnt_v,cnt_oe,fa[MAXN],rely[MAXN];
int tree_p,tree_w[MAXN];
int deepth[MAXN],faa[MAXN][24],lg[MAXN];
bool vis[MAXN];
bool cmp_v(node a,node b)
{
return a.h>b.h;
}
void init()//初始化
{
memset(head_e, 0, sizeof(int) * (n + 1));
memset(head_oe, 0, sizeof(int) * (n << 1));
memset(d,0x3f,sizeof(long long)*(n+1));
memset(vis,0,sizeof(bool)*(n+1));
memset(tree_w,0,sizeof(int)*(n<<1));
memset(rely,0,sizeof(int)*(n+1));
memset(ans,0x3f,sizeof(long long)*(n<<1));
cnt_e= cnt_v= cnt_oe=0;
lastans=0;
tree_p=n;
for(int i=1;i<=(n<<1);i++)fa[i]=i;
}
inline int get(int x)//并查集找父节点
{
if(fa[x]==x)return x;
else return fa[x]=get(fa[x]);
}
void add_e(int a,int b,int w)//最短路用加边
{
E[++cnt_e].v=b;
E[cnt_e].w=w;
E[cnt_e].next=head_e[a];
head_e[a]=cnt_e;
}
void add_v(int a,int b,int h)//生成树用加边
{
V[++cnt_v].u=a;
V[cnt_v].v=b;
V[cnt_v].h=h;
}
void add_oe(int a,int b)//重构树用加边
{
OE[++cnt_oe].v=b;
OE[cnt_oe].next=head_oe[a];
head_oe[a]=cnt_oe;
}
void dijkstra()
{
d[1]=0;
priority_queue<dij,vector<dij>,greater<dij> > Q;
Q.push({0, 1});
while(!Q.empty())
{
int now=Q.top().u;
Q.pop();
if(vis[now])continue;
vis[now]=1;
for(int i=head_e[now]; i; i=E[i].next)
{
int v=E[i].v;
int w=E[i].w;
if(d[v]>d[now]+w)
{
d[v]=d[now]+w;
Q.push({d[v], v});
}
}
}
}
void kruskal()
{
int tmp=0;
for(int i=1;i<=m;i++)
{
int x=V[i].u,y=V[i].v,h=V[i].h;
int tx=get(x),ty=get(y);//并查集操作
if(tx==ty)continue;
tmp++;
int mubiao=++tree_p;
tree_w[mubiao]=h;
add_oe(tx,mubiao);//第一次写kruskal重构树,一堆加边操作
add_oe(mubiao,tx);
add_oe(ty,mubiao);
add_oe(mubiao,ty);
fa[tx]=mubiao;//并查集合并
fa[ty]=mubiao;
if(!rely[x])rely[x]=mubiao;//记录重构树中其父节点
if(!rely[y])rely[y]=mubiao;
if(tmp==n-1)break;
}
}
void dfs_bz(int u,int from)
{
faa[u][0]=from;//无倍增时祖先为其父节点
deepth[u]=deepth[from]+1;//深度+1
for(int i=1;i<=lg[deepth[u]];i++)
{
faa[u][i]=faa[faa[u][i-1]][i-1];//对其父节点的倍增预处理
}
for(int i=head_oe[u]; i; i=OE[i].next)
{
int to=OE[i].v;
if(to<=n)continue;
if(to==from)continue;
dfs_bz(to,u);
}
}
int find_u()
{
int tmp=rely[st];//tmp为实点在重构树中的父虚点
while(!(tree_w[faa[tmp][0]]<=P&&tree_w[tmp]>P))//如果本节点不是深度最小的,且海拔大于P的节点
{
for(int i=lg[deepth[tmp]-1];i>=0;i--)
{
if(tree_w[faa[tmp][i]]>P)//倍增寻找海拔大于P的祖先节点
{
tmp=faa[tmp][i];
break;
}
}
if(tmp==tree_p)break;//如果已经是根节点,则break
}
return tmp;
}
void dfs_ans(int u,int from)
{
for(int i=head_oe[u]; i; i=OE[i].next)
{
int v=OE[i].v;
if(v==from)continue;
if(v<=n)
{
ans[u]=min(ans[u],d[v]);//如果是实点,则更新本虚点的权值
}
else
{
dfs_ans(v,u);//如果是虚点,则先继续深搜
ans[u]=min(ans[u],ans[v]);//然后用其更新本点权值
}
}
}
int main() {
for(int i=1;i<MAXN;++i){
lg[i]=lg[i-1]+(1<<lg[i-1]==i);
}
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
scanf("%d",&m);
init();
for(int i=1;i<=m;i++)
{
int u,v,w,h;
scanf("%d",&u);
scanf("%d",&v);
scanf("%d",&w);
scanf("%d",&h);
add_e(u,v,w);
add_e(v,u,w);
add_v(u,v,h);
}
dijkstra();
sort(V+1,V+m+1,cmp_v);
kruskal();
scanf("%d",&q);
scanf("%d",&K);
scanf("%d",&S);
deepth[0]=0;
dfs_bz(tree_p,0);
dfs_ans(tree_p,0);
while(q--)
{
int v0,p0;
scanf("%d",&v0);
scanf("%d",&p0);
st=(v0+K*lastans-1)%n+1;
P=(p0+K*lastans)%(S+1);
if(P==0)//水位线为0特判
{
printf("0\n");
lastans=0;
continue;
}
if(tree_w[rely[st]]<=P)//出发点不能坐车去任意点特判
{
printf("%lld\n",d[st]);
lastans=d[st];
continue;
}
int u=find_u();
printf("%lld\n",ans[u]);
lastans=ans[u];
}
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!