bzoj2750 Road 最短路&记忆化搜索

       果然直接记忆化很慢啊。。还是spfa的缘故?

      显然这种题目跟定要求多源最短路,但是首先发现n=1500所以不能用floyd了,因此只能用dijkstra+heap或者spfa了(窝比较懒所以用了spfa)。然后我们不妨对一个点i,以i为源点更新答案。先跑一遍最短路,然后统计任意一个点x,从源点i到x有多少条最短路l[x],以及从i->x之后再从x出发能够得到多少条最短路(包括到x自己)r[x],用记忆化搜索得到(或者拓扑排序也可以)。然后对于一条边{x,y,len},如果d[x]+len=d[y](注意单向边),则ans+=l[x]*r[x]。

       然后就做完了,spfa(期望)或斐波那契堆时间复杂度O(NM),dj+普通堆O(NMlogN)。

AC代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#define inf 1000000000
#define mod 1000000007
#define N 20005
#define ll long long
using namespace std;

int n,m,tot,fst[N],pnt[N],len[N],nxt[N],d[N],h[N],sum[N],val[N],ans[N];
bool bo[N];
void add(int aa,int bb,int cc){
	pnt[++tot]=bb; len[tot]=cc; nxt[tot]=fst[aa]; fst[aa]=tot;
}
void spfa(int sta){
	int i,head=0,tail=1; h[1]=sta;
	memset(bo,1,sizeof(bo));
	for (i=1; i<=n; i++) d[i]=inf; d[sta]=0;
	while (head!=tail){
		head=head%7000+1; int x=h[head],p; bo[x]=1;
		for (p=fst[x]; p; p=nxt[p]) if (len[p]){
			int y=pnt[p];
			if (d[x]+len[p]<d[y]){
				d[y]=d[x]+len[p]; if (bo[y]){
					bo[y]=0; tail=tail%7000+1; h[tail]=y;
				}
			}
		}
	}
}
void dp1(int x){
	if (sum[x] || d[x]==inf) return; int p;
	for (p=fst[x]; p; p=nxt[p]) if (!len[p]){
		int y=pnt[p]; if (d[y]+len[p-1]!=d[x]) continue;
		dp1(y); sum[x]=(sum[x]+sum[y])%mod;
	}
}
void dp2(int x){
	if (val[x] || d[x]==inf) return; int p; val[x]=1;
	for (p=fst[x]; p; p=nxt[p]) if (len[p]){
		int y=pnt[p]; if (d[x]+len[p]!=d[y]) continue;
		dp2(y); val[x]=(val[x]+val[y])%mod;
	}
}
int main(){
	scanf("%d%d",&n,&m); int i,j;
	for (i=1; i<=m; i++){
		int x,y,z; scanf("%d%d%d",&x,&y,&z);
		add(x,y,z); add(y,x,0);
	}
	for (i=1; i<=n; i++){
		spfa(i);
		for (j=1; j<=n; j++){ sum[j]=val[j]=0; } sum[i]=1;
		for (j=1; j<=n; j++) dp1(j); for (j=1; j<=n; j++) dp2(j);
		for (j=1; j<=m; j++){
			int x=pnt[j<<1],y=pnt[(j<<1)-1];
			if (d[x]+len[(j<<1)-1]!=d[y]) continue;
			ans[j]=((ll)sum[x]*val[y]%mod+ans[j])%mod;
		}
	}
	for (i=1; i<=m; i++) printf("%d\n",ans[i]);
	return 0;
}

by lych
2016.2.17

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值