NOI 2018 Day1 T1 归程

题面见洛谷

难点:  走过有积水的地方之后就需计算路径长了

关键算法:   kruskal重构树

①原来的 kruskalkruskalkruskal 算法就是用并查集实现的,

但当我们使用 kruskal重构树的时候,

对于每次找出的不同的两个连通块的祖先,

我们都新建一个点作为两个祖先的父亲,并将当前边的边权转化为新点的点权(或指向该点的边的边权)。

②因为kruskal是贪心加边,所以对于该题来说,

如果在重构树上能从一个点抵达另一个点,那么在原图上也一定可以

③如果我们以海拔为第一关键字对边进行从大到小的排序,然后修建 kruskal重构树,

这样就弄出了一颗以海拔为关键字的小根堆。

然后对于每一棵子树,如果询问中的水位线是低于子树的根节点的,

那么此时这棵子树中的所有叶子结点都是连通的。

放到题中就是说这颗子树中任选一个点出发,

到子树中的其它点都不需要花费。(此段来自洛谷题解)

④对于每个询问,我们只需要找到该点无需花费就能走到的点(用预处理好的倍增找)中,哪个离目的地(1号点)更近,

这个预处理一下最短路就是了

上代码:

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#define rg register
#define _ 200001
#define a(x) edge[x].a
#define l(x) edge[x].l
#define u(x) heap[x].u
#define it(x) heap[x].it
#define min(x,y) x<y?x:y;
using namespace std;
int n,m,record[_<<1],exist[_],num_of_edges,an[_<<1][22],minn[_<<1][22],cnt,dad[_<<1];
long long dis[_],New_dis[_<<1];
bool used[_];
struct ppp
{
    int x,y,a;
}way[_<<1];
struct pp
{
    int next,to,l,a;
}edge[_<<3];
struct pppp
{
    int u,it;
}heap[800001];
inline int read()
{
    rg int save=0,w=1;rg char q=getchar();
    while(q<'0'||q>'9'){if(q=='-')w=-1;q=getchar();}
    while(q>='0'&&q<='9')save=(save<<3)+(save<<1)+q-'0',q=getchar();
    return save*w;
}
inline void add(rg int from,rg int to,rg int ll,rg int aa)
{
    edge[++num_of_edges]=(pp){record[from],to,ll,aa};
    record[from]=num_of_edges;
}
int tail;
inline void put(rg int i,rg int ww)
{
    u(++tail)=i,it(tail)=ww;
    rg int d=tail;
    while(d>1)
    {
        if(it(d)<it(d>>1))swap(heap[d],heap[d>>1]),d>>=1;
        else
            break;
    }
}
inline void cut()
{
    heap[1]=heap[tail--];
    it(tail+1)=2147483647;
    rg int d=1;
    while(d<tail)
    {
        rg int pointer=it(d<<1)<it(d<<1|1)?(d<<1):(d<<1|1);
        if(it(d)>it(pointer))swap(heap[d],heap[pointer]),d=pointer;
        else break;
    }
}
int find(rg int x){if(x!=dad[x])dad[x]=find(dad[x]);return dad[x];}
inline void dijkstra()
{
    for(rg int i=1;i<=n;++i)used[i]=0;
    dis[1]=0;
    put(1,dis[1]);
    rg int k=0;
    while(tail)
    {
        rg pppp ss=heap[1];
        cut();
        if(used[ss.u])continue;
        used[ss.u]=1;
        k++;
        for(rg int j=record[ss.u];j;j=edge[j].next)
        {
            rg int to=edge[j].to;
            if(dis[to]>dis[ss.u]+edge[j].l)
            {
                dis[to]=dis[ss.u]+edge[j].l;
                put(to,dis[to]);
            }
        }
        if(k==n-1)break;
    }
}
inline bool Cwen(rg ppp x,rg ppp y){return x.a>y.a;}
int ceng;
void dfs(rg int);
inline void Kruskal()
{
    rg int i,j;
    for(i=1;i<=(n<<1);++i)dad[i]=i;
    sort(way+1,way+m+1,Cwen);
    for(i=1;i<=m;++i)
    {
        rg int fx=find(way[i].x),fy=find(way[i].y);
        if(fx!=fy)//重构树
        {
            add(++cnt,fx,0,way[i].a);//(++cnt):新建节点
            add(cnt,fy,0,way[i].a);//因为之后的操作只需要从根节点遍历下来,故不建反向边
            dad[fx]=dad[fy]=cnt;
            if(cnt==(n<<1)-1)break;
        }
    }
    ceng=log(cnt)/log(2);
    for(i=1;i<=cnt;++i)
    {
        if(i<=n)New_dis[i]=dis[i];
        for(j=1;j<=ceng;++j)
            an[i][j]=0,minn[i][j]=2147483647;
    }
    for(i=n+1;i<=cnt;++i)New_dis[i]=2147483647;
    //这个题目里不要找LCA,depth[]也就不需要
    an[cnt][0]=0;//聊胜于无的一句话
    dfs(cnt);//预处理点之间的最小海拔
}
void dfs(rg int i)
{
    for(rg int j=1;j<=ceng;++j)
    {
        an[i][j]=an[an[i][j-1]][j-1];
        minn[i][j]=min(minn[i][j-1],minn[an[i][j-1]][j-1]);
    }
    if(i<=n)return;//免得走到原来的图上了(在重构树里<=n的就是叶子节点,无需继续遍历)
    for(rg int j=record[i];j;j=edge[j].next)
    {
        rg int to=edge[j].to;
        if(to!=an[i][0])
        {
            an[to][0]=i;
            minn[to][0]=a(j);
            dfs(to);
            New_dis[i]=min(New_dis[i],New_dis[to]);
        }
    }
}
inline int jump(rg int i,rg int p)
{
    for(rg int j=ceng;j>=0;--j)
        if(minn[i][j]>p)i=an[i][j];
    return i;
}
int main()
{
    rg int t=read();
    while(t--)
    {
        n=read(),m=read();
        rg int i,j;
        tail=0;
        for(i=1;i<=n;++i)dis[i]=2147483647;
        for(i=1;i<=(n<<2);++i)it(i)=2147483647;
        num_of_edges=0;
        for(i=1;i<=(n<<1);++i)record[i]=0;
        for(i=1;i<=m;++i)
        {
            rg int u=read(),v=read(),l=read(),a=read();
            add(u,v,l,a),add(v,u,l,a);
            way[i]=(ppp){u,v,a};
        }
        dijkstra();
        cnt=n;
        Kruskal();//重构树,以海拔为关键字从大到小排序,保证可以判断一个点是否能被无耗遍历到
        rg int Q=read(),K=read(),S=read();
        long long lastans=0;
        for(i=1;i<=Q;++i)
        {
            rg int v=read(),p=read();
            v=(v+K*lastans-1)%n+1;
            p=(p+K*lastans)%(S+1);
            rg int to=jump(v,p);
            lastans=New_dis[to];
            printf("%lld
",lastans);
        }
        
    }
    return 0;
}
原文地址:https://www.cnblogs.com/c-wen/p/9346135.html