• graph


    【from new_dtoj 3970: graph】
    题目描述
    小f 的女朋友送给小f 一个有n个点m条边的无向图。但是这个无向图太大了,小f 的女朋友拿不动,于是小f 希望只保留图的一部分。在这张图上,对于第i条边ui,vi,从ui 到vi的代价为ai,从vi到ui的代价为bi。
    小f 希望只保留一个包含1号点的有向环(不能有重复的点),使得环上代价之和最小。
    输入
    第一行两个正整数表示n,m。
    接下来m行,每行4个数,分别代表ui,vi,ai,bi。
    输出
    一行一个数,表示最小的代价。
    样例输入
    样例输入1

    3 3
    1 2 1 1000
    2 3 1 1000
    1 3 10000 1
    样例输入2
    13 15
    1 2 5 5
    2 3 10 10
    3 4 5 5
    4 5 100 100
    5 6 20 20
    6 7 17 17
    7 2 15 15
    5 9 2000 2000
    9 10 8 8
    10 11 7 7
    11 12 8 8
    12 13 7 7
    13 8 8 8
    8 9 7 7
    1 12 10 10
    样例输出
    输出样例1

    3
    输出样例2
    2089
    提示
    样例解释1
    最小的环为1→2,2→3,3→1。
    数据范围
    对于前30%的数据,n,m≤50;
    对于前75%的数据,n,m≤5000;
    对于100%的数据,n≤3×1043×10^4,2≤m≤10510^5,0≤ai,bi≤10410^4
    题解:
    最开始的想法,把1 号点拆成两个点,一个连全部的入边,一个连全部的出边,直接最短路。但这个算法明显错误,因为与1 号点相邻的点可能出现重复走的情况。所以就会想到说与1号点相连的点是起点/终点。于是可以暴力出哪一些点是起点,哪一些点是终点,然后求出min(dist(1u)+dist(uv)+dist(v1))min(dist(1→u)+dist(u→v)+dist(v→1))但是这样只能过30分(貌似可以卡常到65?)
    但是事实上我们只需要分出一些情况,使得对于任意(u,v)
    1.都当做是起点
    2.都当做是终点
    3.一个是起点,一个是终点
    巧妙的二进制分组,也就是说因为对于任意的编号是不同的,所以枚举二进制位k,把第k位上是1的点当做起点,把为0的点当做终点,再反过来,把第k位上是0的点当做起点,把为1的点当做终点,这样所有的情况就都保证了
    现在的问题在于每次分组,怎样只跑一遍最短路就可以就出min(dist(1u)+dist(uv)+dist(v1))min(dist(1→u)+dist(u→v)+dist(v→1))于是我们多加一个点,编号为n+1,从1连向u,从v连向n+1,所以问题就等价于求出1到n+1的最短路了
    于是就只要走lognlog n遍最短路
    代码如下:

    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <queue>
    #define _(d) while(d(isdigit(c=getchar())))
    #define I inline
    using namespace std;
    I int R(){
        int x,f=1;char c;_(!)c=='-'?f=0:f;x=(c^48);
        _()x=(x<<3)+(x<<1)+(c^48);return f?x:-x;
    }
    const int M=1e5+5,N=3e4+5;
    int n,m,h[N],V[M*2],W[M*2],nex[M*2],h2[N],c,t,tt,ans=1e9,d[N];
    bool vis[N];struct O{int x,y,a,b;}p[N];queue<int>q;
    I void add(int u,int v,int w,bool ty){
        V[++t]=v;W[t]=w;
        if (ty) nex[t]=h[u],h[u]=t;
        else nex[t]=h2[u];h2[u]=t;
    }
    I void S(){
        for (int i=1;i<=n+1;i++) d[i]=1e9,vis[i]=0;
        q.push(1);d[1]=0;vis[1]=1;
        while(!q.empty()){
            int x=q.front();q.pop();vis[x]=0;
            for (int i=h[x];i;i=nex[i])
                if (d[V[i]]>d[x]+W[i]){
                    d[V[i]]=d[x]+W[i];
                    if (!vis[V[i]]) q.push(V[i]),vis[V[i]]=1;
                }
            for (int i=h2[x];i;i=nex[i])
                if (d[V[i]]>d[x]+W[i]){
                    d[V[i]]=d[x]+W[i];
                    if (!vis[V[i]]) q.push(V[i]),vis[V[i]]=1;
                }
        }
        if (d[n+1]<ans) ans=d[n+1];
    }
    int main(){
        n=R();m=R();
        for (int x,y,a,b,i=1;i<=m;i++){
            x=R();y=R();a=R();b=R();
            if (x-1 && y-1) add(x,y,a,1),add(y,x,b,1);
            else{
                if (x>y) swap(x,y),swap(a,b);
                p[++tt]=(O){x,y,a,b};
            }
        }
        c=t;
        for (int i=0;(1<<i)<=n;i++){
            memset(h2,0,sizeof(h2));t=c;
            for (int k,j=1;j<=tt;j++){
                if ((1<<i)&p[j].y) add(1,p[j].y,p[j].a,0);
                else add(p[j].y,n+1,p[j].b,0);
            }
            S();
            memset(h2,0,sizeof(h2));t=c;
            for (int k,j=1;j<=tt;j++){
                if ((1<<i)&p[j].y) add(p[j].y,n+1,p[j].b,0);
                else add(1,p[j].y,p[j].a,0);
            }
            S();
        }
        return printf("%d
    ",ans),0;
    }
    
  • 相关阅读:
    Scite 编辑器及其相关项目介绍
    cmake 常用指令入门指南
    C++中的POD类型
    引用折叠、万能引用和完美转发那些事
    c++的对象初始化
    C++类对象的内存布局
    effective C++ 读书精华笔记提取
    c/c++的const说明符——深入挖掘
    gdb调试器—常用知识(一)
    g++/gcc编译器——常用知识(一)
  • 原文地址:https://www.cnblogs.com/xjqxjq/p/10544729.html
Copyright © 2020-2023  润新知