zoukankan      html  css  js  c++  java
  • [SDOI2009]Elaxia的路线

    传送门

    现在看看自己早期的题解真是惨不忍睹...(upd in 2019.8.11)

    算法 最短路+DAG上的dp+建图

    思路

    首先是求最短路,但是题目没给出边的范围,所以我选用 $Dijkstra$ 求最短路,SPFA可能会爆炸

    先分别求出以两个人为起点的单源最短路径,然后判断一条边 $(a,b)$ 是否在最短路径上只要判断 $dis[a]+map[a][b]$ 是否等于 $dis[b]$

    其中 $map[a][b]$ 表示从点a到点b的 直接 距离 

    貌似不用走四遍最短路..

    找出最短路径(注意是路径不是路程)后用其中一个人的最短路径建一个图:

    如果这个人的最短路径有一种方案使得路径经过 $(a,b)$ 就从 $a$ 连一条边到 $b$,显然这个图是 $DAG$,然后就可以考虑在 $DAG$ 上跑 $dp$ 了

    具体就是对于新图的一边连接的两点 $a,b$,如果边 $(a,b)$ 同时在另一个人的最短路径上,那么 $f[b]=max(f[b],f[a]+map[a][b])$

    其中 $f[i]$ 表示以 $i$ 为终点的连续的公共路径的最大长度,最后枚举每个点的 $f[i]$ 取 $max$ 就是答案了

    顺便付上自己搞的的样例图

    具体操作代码里注释还是挺细的吧,因为只要两遍最短路所以常数比较优秀 ?

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<algorithm>
    #include<cstring>
    #include<vector>
    #include<queue>
    using namespace std;
    inline int read() //顺手打一个快读 
    {
        int res=0;
        char ch=getchar();
        while(ch>'9'||ch<'0')
            ch=getchar();
        while(ch>='0'&&ch<='9')
        {
            res=res*10+ch-'0';
            ch=getchar();
        }
        return res;
    }
    struct node//为Dijkstra的优先队列开的结构体 
    {
        int u,v;//v存储节点编号,u存储到当时点v的最短路径 
        bool operator < (const node &b) const{
            return u>b.u;
        }//重载运算符 
    };
    priority_queue <node> q;//Dijkstra的优先队列
    struct edge
    {
        int from,to;
    }e[5000005];
    int fir[1505],cnt;//存边(边数可能较大,用链式前向星存边会比vector快一些) 
    int map[1505][1505];//map[i][j]存从点i到点j的直接距离 
    inline void add(int a,int b,int c)
    {
        e[++cnt].from=fir[a];
        fir[a]=cnt;
        e[cnt].to=b;
        map[a][b]=c;
    }//链式前向星加边 
    int xa,ya,xb,yb,n,m;
    int dis[1505][2];
    //dis[i][0]存 Elaxia从xa到各点的距离,dis[i][1]存w**从xb到各点的距离
    inline void dijk(int sta,int k)//Dijkstra求最短路 
    {
        dis[sta][k]=0;
        node p;
        p.u=0; p.v=sta;
        q.push(p);
        while(q.empty()==0)
        {
            int u=q.top().u,v=q.top().v;
            q.pop();//出队 
            if(u!=dis[v][k]) continue;//优化 
            for(int i=fir[v];i;i=e[i].from)
            {
                int to=e[i].to;
                if(dis[to][k]>dis[v][k]+map[v][to])//松弛操作 
                {
                    dis[to][k]=dis[v][k]+map[v][to];
                    p.u=dis[to][k]; p.v=to;
                    q.push(p);//入队 
                }
            }
        }
    }
    vector <int> v[1505];//懒得用链式前向星了,直接用vector存Elaxia的最短路线就好了 
    int f[1505],du[1505],ans;
    //f[i]表示以i为终点的最长连续 公共最短路长度,du[i]存入度 
    queue <int> qa;
    bool vis[1505],pd[1505][1505],p[1505];
    //vis存Elaxia最短路线上的点
    //pd[i][j]=1表示w**的最短路线中有经过从i到j的边,p是用来判断节点是否在队列qa中 
    inline void slove()
    {
        memset(dis,0x7f,sizeof(dis));
        dijk(xa,0);
        dijk(xb,1);
        qa.push(ya);
        //反向找出最短路径上的点,保证先出队的节点在Elaxia的最短路径上处于更后的位置
        //从终点开始找,保证找到的满足条件的点一定在Elaxia的最短路上
        vis[ya]=1;
        while(qa.empty()==0)
        {
            int x=qa.front(); qa.pop();
            for(int i=fir[x];i;i=e[i].from)
            {
                int u=e[i].to;
                if(dis[u][0]+map[u][x]==dis[x][0])
                //如果一个节点u满足条件,说明u在Elaxia的最短路上
                {
                    v[u].push_back(x);//从u到点x连一条有向边 
                    du[x]++;//点x的入度加一 
                    if(vis[u]==0)//如果没有加入过 
                    {
                        qa.push(u);//把u加入队列,从而找到更"前面"的点 
                        vis[u]=1;//现在加入过了 
                    }
                }
            }
        }
        //此时qa已经空了,可以重复利用 
        qa.push(yb); //开始找w**的最短路径 
        p[yb]=1;//vis数组要留着,重新开一个数组p 
        while(qa.empty()==0)//同上
        {
            int x=qa.front(); qa.pop();
            for(int i=fir[x];i;i=e[i].from)
            {
                int u=e[i].to;
                if(dis[u][1]+map[u][x]==dis[x][1])//同上 
                {
                    pd[x][u]=pd[u][x]=1;//这次不用连边了,方便后面的判断 
                    //正反都要判断,可能是反着走 
                    if(p[u]==0)
                    {
                        p[u]=1;
                        qa.push(u);
                    }
                }
            }
        }
        //以下为DAG上的dp 
        for(int i=1;i<=n;i++)
            if(du[i]==0&&vis[i])//如果i点入度为零且 Elaxia的最短路经过i
                qa.push(i);//把i加入qa
        while(qa.empty()==0)
        {
            int x=qa.front(); qa.pop();
            int len=v[x].size();
            for(int i=0;i<len;i++)
            {
                int u=v[x][i];//首先能够保证 Elaxia的最短路经过边x->i 
                du[u]--;//入度减一 
                if(pd[x][u]) //如果边x->i也被w**的最短路经过 
                    f[u]=max(f[u],f[x]+map[x][u]);//更新长度 
                if(du[u]==0)
                    qa.push(u);//dp要按照拓扑序来保证这个点前面的所有点都访问过了 
            }
        }
        //以上为DAG上的dp 
        for(int i=1;i<=n;i++)
            ans=max(ans,f[i]);//找出答案 
    }
    int main()
    {
        memset(map,0x7f,sizeof(map));
        cin>>n>>m;
        cin>>xa>>ya>>xb>>yb;
        int a,b,c;
        for(int i=1;i<=m;i++)
        {
            a=read(); b=read(); c=read(); 
            add(a,b,c);
            add(b,a,c);
        }
        slove();
        cout<<ans;
        return 0;//简单易懂的主程序 
    }

     然而洛谷的数据真的水..就过了

    其实这是有问题的(感谢Brave_Cattle 巨佬指出错误)

    有一种可能数据:

    4 4 
    1 4 2 3
    1 2 10
    1 3 1
    4 2 9
    4 3 2

    显然答案是 $2$,但是我的程序输出是$3$...

    因为我没有考虑到最短路径只能选一种走...

    那要怎么解决呢

    也不难,虽然要考虑正反方向,但是显然不会一下正着走,一下反着走

    所以分开讨论一下就好了..然后代码就变得更长了...

    懒得重新写代码了...

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<algorithm>
    #include<cstring>
    #include<vector>
    #include<queue>
    using namespace std;
    inline int read() //顺手打一个快读 
    {
        int res=0;
        char ch=getchar();
        while(ch>'9'||ch<'0')
            ch=getchar();
        while(ch>='0'&&ch<='9')
        {
            res=res*10+ch-'0';
            ch=getchar();
        }
        return res;
    }
    struct node//为Dijkstra的优先队列开的结构体 
    {
        int u,v;//v存储节点编号,u存储到当时点v的最短路径 
        bool operator < (const node &b) const{
            return u>b.u;
        }//重载运算符 
    };
    priority_queue <node> q;//Dijkstra的优先队列
    struct edge
    {
        int from,to;
    }e[5000005];
    int fir[1505],cnt;//存边(边数可能较大,用链式前向星存边会比vector快一些) 
    int map[1505][1505];//map[i][j]存从点i到点j的直接距离 
    inline void add(int a,int b,int c)
    {
        e[++cnt].from=fir[a];
        fir[a]=cnt;
        e[cnt].to=b;
        map[a][b]=c;
    }//链式前向星加边 
    int xa,ya,xb,yb,n,m;
    int dis[1505][2];
    //dis[i][0]存 Elaxia从xa到各点的距离,dis[i][1]存w**从xb到各点的距离
    inline void dijk(int sta,int k)//Dijkstra求最短路 
    {
        dis[sta][k]=0;
        node p;
        p.u=0; p.v=sta;
        q.push(p);
        while(q.empty()==0)
        {
            int u=q.top().u,v=q.top().v;
            q.pop();//出队 
            if(u!=dis[v][k]) continue;//优化 
            for(int i=fir[v];i;i=e[i].from)
            {
                int to=e[i].to;
                if(dis[to][k]>dis[v][k]+map[v][to])//松弛操作 
                {
                    dis[to][k]=dis[v][k]+map[v][to];
                    p.u=dis[to][k]; p.v=to;
                    q.push(p);//入队 
                }
            }
        }
    }
    vector <int> v[1505];//懒得用链式前向星了,直接用vector存Elaxia的最短路线就好了 
    int f[1505],du[1505],ans;
    //f[i]表示以i为终点的最长连续 公共最短路长度,du[i]存入度 
    queue <int> qa;
    bool vis[1505],pd[1505][1505],p[1505];
    //vis存Elaxia最短路线上的点
    //pd[i][j]=1表示w**的最短路线中有经过从i到j的边,p是用来判断节点是否在队列qa中 
    int duu[1505];//du数组的拷贝
    int t[2265025][2],tot;
    inline void slove()
    {
        memset(dis,0x7f,sizeof(dis));
        dijk(xa,0);
        dijk(xb,1);
        qa.push(ya);
        //反向找出最短路径上的点,保证先出队的节点在Elaxia的最短路径上处于更后的位置
        //从终点开始找,保证找到的满足条件的点一定在Elaxia的最短路上
        vis[ya]=1;
        while(qa.empty()==0)
        {
            int x=qa.front(); qa.pop();
            for(int i=fir[x];i;i=e[i].from)
            {
                int u=e[i].to;
                if(dis[u][0]+map[u][x]==dis[x][0])
                //如果一个节点u满足条件,说明u在Elaxia的最短路上
                {
                    v[u].push_back(x);//从u到点x连一条有向边 
                    du[x]++;//点x的入度加一 
                    if(vis[u]==0)//如果没有加入过 
                    {
                        qa.push(u);//把u加入队列,从而找到更"前面"的点 
                        vis[u]=1;//现在加入过了 
                    }
                }
            }
        }
        //此时qa已经空了,可以重复利用 
        qa.push(yb); //开始找w**的最短路径 
        p[yb]=1;//vis数组要留着,重新开一个数组p 
        while(qa.empty()==0)//同上
        {
            int x=qa.front(); qa.pop();
            for(int i=fir[x];i;i=e[i].from)
            {
                int u=e[i].to;
                if(dis[u][1]+map[u][x]==dis[x][1])//同上 
                {
                    pd[u][x]=1;//这次不用连边了,方便后面的判断 
                    t[++tot][0]=u; t[tot][1]=x; //可能路径方向相反,先用一个数组存着
                    if(p[u]==0)
                    {
                        p[u]=1;
                        qa.push(u);
                    }
                }
            }
        }
    
        //以下为DAG上的dp 
        //第一遍先找方向相同的路径
        for(int i=1;i<=n;i++)
        {
            if(du[i]==0&&vis[i])//如果i点入度为零且 Elaxia的最短路经过i
                qa.push(i);//把i加入qa
            duu[i]=du[i];//拷贝一下du,因为要用两次
        }
        while(qa.empty()==0)
        {
            int x=qa.front(); qa.pop();
            int len=v[x].size();
            for(int i=0;i<len;i++)
            {
                int u=v[x][i];//首先能够保证 Elaxia的最短路经过边x->v[x][i] 
                duu[u]--;//入度减一
                if(pd[x][u]) //如果边x->i也被w**的最短路经过 
                    f[u]=max(f[u],f[x]+map[x][u]);//更新长度 
                if(duu[u]==0)
                    qa.push(u);//dp要按照拓扑序来保证这个点前面的所有点都访问过了 
            }
        }
        for(int i=1;i<=n;i++)
            ans=max(ans,f[i]);//更新答案
        
        //第二遍找方向相反的路径,同上
        memset(f,0,sizeof(f));
        for(int i=1;i<=tot;i++)
        {
            pd[t[i][0]][t[i][1]]=0;
            pd[t[i][1]][t[i][0]]=1; //把路径换个方向
        }
        for(int i=1;i<=n;i++)
            if(du[i]==0&&vis[i])
                qa.push(i);//同上
        while(qa.empty()==0)
        {
            int x=qa.front(); qa.pop();
            int len=v[x].size();
            for(int i=0;i<len;i++)
            {
                int u=v[x][i];
                du[u]--;
                if(pd[x][u])
                    f[u]=max(f[u],f[x]+map[x][u]);
                if(du[u]==0)
                    qa.push(u);
            }
        }//同上
    
        //以上为DAG上的dp 
        for(int i=1;i<=n;i++)
            ans=max(ans,f[i]);
    }
    int main()
    {
        memset(map,0x7f,sizeof(map));
        cin>>n>>m;
        cin>>xa>>ya>>xb>>yb;
        int a,b,c;
        for(int i=1;i<=m;i++)
        {
            a=read(); b=read(); c=read(); 
            add(a,b,c);
            add(b,a,c);
        }
        slove();
        cout<<ans;
        return 0;//简单易懂的主程序 
    }
  • 相关阅读:
    8086标志
    微内核
    枚举算法
    ajax
    面向对象技术概述
    ajax
    存储技术
    自然数组排列
    将搜索二叉树转换成双向链表
    在单链表中删除指定值的节点
  • 原文地址:https://www.cnblogs.com/LLTYYC/p/9506297.html
Copyright © 2011-2022 走看看