星星之火

[BZOJ4289] [PA2012] Tax 解题报告 (最短路+差分建图)

题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=4289

4289: PA2012 Tax

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 1029  Solved: 310
[Submit][Status][Discuss]

Description

给出一个N个点M条边的无向图,经过一个点的代价是进入和离开这个点的两条边的边权的较大值,求从起点1到点N的最小代价。起点的代价是离开起点的边的边权,终点的代价是进入终点的边的边权
N<=100000
M<=200000

Sample Input

4 5
1 2 5
1 3 2
2 3 1
2 4 4
3 4 8

Sample Output

12

                             [Submit][Status][Discuss]
 

比较有技巧的建图

首先考虑暴力点的建图:

把每条无向边拆成两条有向边.把每条边看成一个点,对于两条边a->b,b->c

在这两条边之间连有向边,边权为这两条边的权值的较大值.

新建源点S,汇点T, S向所有从1连出去的边连边,所有指向n的边向T连边. 求S->T的最短路即可.

这样的复杂度会达到O(m2)

考虑优化一下:

考虑利用差值来建边.

依然把每条边x-y拆成x->y,y->x.

枚举每个中转点x. 将x的出边按权值排序,x的每条入边向对应的出边连该边权值的边(i对应i^1)),x的每条出边向第一个比它大的出边连两边权差值的边,x的每条出边向第一个比它小的出边连权值为0的边. 新建源汇S,T S向每条1的出边连权值为该边边权的边.每条n的入边向T连该边权值的边.

 

仔细看图,L2>L1,发现从S到T的路径走的一定是L2,而不是L1;注意结合上述的建图过程(红色边)

跑S->T的最短路即可.

这样的复杂度是O(mlogm)就可以AC

我的SPFA被T了,估计没写错只是被卡了

以上内容部分参考DaD3zZ大佬的博客

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#define ll long long
using namespace std;

const int N=200000+15;
int n,m,tot1,tot2,S,T;
int head[N],h[N<<1],st[N<<1],vis[N<<1];
ll dist[N<<1];
struct EDGE
{
    int to;int next;int l;
}edge[N<<1],e[N<<3];
inline int read()
{
    char ch=getchar();
    int s=0,f=1;
    while (!(ch>='0'&&ch<='9')) {if (ch=='-') f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9') {s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
    return s*f;
}
void init()
{
    memset(head,-1,sizeof(head));
    memset(h,-1,sizeof(h));
    memset(dist,0x3f3f3f3f,sizeof(dist));
}
void addedge(int x,int y,int l)
{
    edge[tot1]=(EDGE){y,head[x],l};
    head[x]=tot1++;
}
bool cmp(int x,int y) {return edge[x].l<edge[y].l;}
void addroad(int x,int y,int l)
{
    e[tot2]=(EDGE){y,h[x],l};
    h[x]=tot2++;
}
void build()
{
    S=2*(m+1)+15;T=2*(m+1)+16;
    for (int i=1;i<=n;i++)
    {
        int tp=0;
        for (int j=head[i];j!=-1;j=edge[j].next)
            st[++tp]=j;
        sort(st+1,st+1+tp,cmp);
        for (int j=1;j<=tp;j++)
        {
            int now=st[j],suc=st[j+1];    
            if (edge[now].to==n) addroad(now,T,edge[now].l);
            if (i==1) addroad(S,now,edge[now].l);
            addroad(now^1,now,edge[now].l);
            if (j<tp) {addroad(now,suc,edge[suc].l-edge[now].l);addroad(suc,now,0);}
        }
    }
}
priority_queue<pair<ll,int>,vector<pair<ll,int> >,greater<pair<ll,int> > > q;
void Dijkstra()
{
    q.push(make_pair(0,S)); 
    dist[S]=0;
    while (!q.empty())
    {
        int now=q.top().second;
        ll Dis=q.top().first;q.pop();
        if (Dis>dist[now]) continue;
        for (int i=h[now];i!=-1;i=e[i].next)
            if (dist[now]+e[i].l<dist[e[i].to])
            {
                dist[e[i].to]=dist[now]+e[i].l;
                q.push(make_pair(dist[e[i].to],e[i].to));
            }
    }
}
int main()
{
    n=read();m=read();
    init();
    for (int i=1;i<=m;i++)
    {
        int x=read(),y=read(),l=read();
        addedge(x,y,l);addedge(y,x,l);
    }
    build();
    Dijkstra();
    printf("%lld\n",dist[T]);
    return 0;
}

 

posted @ 2018-07-25 14:57  星星之火OIer  阅读(230)  评论(0编辑  收藏  举报