4289: PA2012 Tax
Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 701 Solved: 222
[ Submit][ Status][ Discuss]
Description
给出一个N个点M条边的无向图,经过一个点的代价是进入和离开这个点的两条边的边权的较大值,求从起点1到点N的最小代价。起点的代价是离开起点的边的边权,终点的代价是进入终点的边的边权
N<=100000
M<=200000
Input
Output
Sample Input
4 5
1 2 5
1 3 2
2 3 1
2 4 4
3 4 8
1 2 5
1 3 2
2 3 1
2 4 4
3 4 8
Sample Output
12
这题有个显然的暴力做法,就是把每条无向边拆成两条有向边,然后把这些有向边当做点,重新建图,对于原图中的点x,每一条入边p,要向每一条出边q连边,边的权值为p和q这两条边权值的较大值,补上源点和汇点,跑最短路就可以了。暴力的效率是O(m*m)。
我们考虑进行优化。对于暴力的做法,我们发现其中每条入边要向每条出边(这里指的入边和出边都转化为后来的点)连边,这一步直接制约了我们的效率。我们考虑如何优化这一步:
对于一个点x,我们把所有的出边按权值排序,容易知道,我们可以从权值小的边向权值大的边(这里指的连边都是把原图里的边转化为点后进行的)连边,边的权值是大权值-小权值,而对于权值大的边到权值小的边我们可以连一条权值为0的边(因为如果经过了权值较大的边,那么再经过权值较小的边时对答案的贡献仍然是那个较大的权值)。之后我们把入边加入,每条入边向对应的出边(y->x向x->y)连一条权值为原图中改边权值的边。
这样我们就把建图简化了,再加上源点和汇点就可以跑最短路了。
#include<cstdio>
#include<queue>
#include<vector>
#include<algorithm>
#include<cstring>
#define Pa pair<long long,int>
#define ll long long
#define N 2000005
#define INF 1LL<<60
using namespace std;
int k=1,K=1,fir[N],Fir[N],st,ed,l,r,c,n,m,b[N];
ll dis[N];
struct he{
int r,c,nx;
}a[N],A[N];
bool cmp(int u,int v){
return a[u].c<a[v].c;
}
void add(int l,int r,int c){
k++;a[k].r=r;a[k].c=c;a[k].nx=fir[l];fir[l]=k;
}
void Add(int l,int r,int c){
K++;A[K].r=r;A[K].c=c;A[K].nx=Fir[l];Fir[l]=K;
}
ll dij(int st,int ed){
priority_queue<Pa,vector<Pa>,greater<Pa> >Q;
for(int i=st;i<=ed;i++) dis[i]=INF;
Q.push(make_pair(0,st));dis[st]=0;
while(!Q.empty()){
int u=Q.top().second;
ll Dis=Q.top().first;
Q.pop();
if(Dis>dis[u]) continue;
for(int i=Fir[u];i!=-1;i=A[i].nx)
if(dis[u]+A[i].c<dis[A[i].r])
dis[A[i].r]=dis[u]+A[i].c,Q.push(make_pair(dis[A[i].r],A[i].r));
}
return dis[ed];
}
void rebuild(){
st=1;ed=2*(m+1);
for(int i=1;i<=n;i++){
int cnt=0;
for(int j=fir[i];j!=-1;j=a[j].nx)
b[++cnt]=j;
sort(b+1,b+1+cnt,cmp);
for(int j=1;j<=cnt;j++){
int x=b[j],y=b[j+1];
if(a[x].r==n) Add(x,ed,a[x].c);
if(i==1) Add(st,x,a[x].c);
Add(x^1,x,a[x].c);
if(j<cnt) Add(x,y,a[y].c-a[x].c),Add(y,x,0);
}
}
}
int main(){
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
memset(fir,-1,sizeof(fir));
memset(Fir,-1,sizeof(Fir));
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&l,&r,&c);
add(l,r,c);add(r,l,c);
}
rebuild();
printf("%lld",dij(st,ed));
}