• 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;
    }
  • 相关阅读:
    socket套接字
    popen:让进程看似文件
    fdopen:让文件描述符像文件一样使用
    计算机"右击"管理,不出现界面,解决方案
    javaEE版本的eclipse中导入工程,发现server里面找不到工程,根本发布不了也不能运行
    初识springMVC
    数据库系统
    Red hat 下nfs服务器的搭建
    Linux下MySQL安装和配置
    复习Hibernate(1)
  • 原文地址:https://www.cnblogs.com/c-wen/p/9346135.html
Copyright © 2020-2023  润新知