P2505 [HAOI2012]道路

传送门

统计每条边被最短路经过几次,点数不大,考虑计算以每个点为起点时对其他边的贡献

对于某个点 $S$ 为起点的贡献,首先跑一遍最短路,建出最短路的 $DAG$

考虑 $DAG$ 上的某条边被以 $S$ 为起点的最短路经过的方案数,设此边为 $(u,v)$ ,那么方案数就是 $S$ 到 $u$ 的方案数,乘上 $v$ 到后面各点的方案数

$S$ 到 $u$ 的方案数可以按拓扑序 $dp$ 一遍得到,$v$ 到后面各点的方案数可以建反图再跑一遍 $dp$

然后就可以计算起点 $S$ 对各条边的贡献,对每个点作为起点分别计算贡献即可

具体实现看代码,挺简单的

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=2007,M=1e4+7,mo=1e9+7;
inline int fk(int x) { return x>=mo ? x-mo : x; }
int n,m;
ll ans[M];
int fir[N],from[M<<1],to[M<<1],val[M<<1],id[M<<1],cntt;
inline void add(int a,int b,int c,int d)
{
    from[++cntt]=fir[a]; fir[a]=cntt;
    to[cntt]=b; val[cntt]=c; id[cntt]=d;
}
int dis[N];
struct dat {
    int x,d;
    dat (int a=0,int b=0) { x=a,d=b; }
    inline bool operator < (const dat &tmp) const {
        return d>tmp.d;
    }
};
priority_queue <dat> Q;
void Dijk(int S)//求以S为起点到各个点的最短路
{
    for(int i=1;i<=n;i++) dis[i]=mo;
    Q.push(dat(S,0)); dis[S]=0;
    while(!Q.empty())
    {
        dat x=Q.top(); Q.pop(); if(dis[x.x]!=x.d) continue;
        for(int i=fir[x.x];i;i=from[i])
        {
            int &v=to[i]; if(dis[v]<=x.d+val[i]) continue;
            dis[v]=x.d+val[i]; Q.push(dat(v,dis[v]));
        }
    }
}
vector <int> V[N],G[N];//V存DAG
int du[N],f[N],g[N];//入度,S到各个点的方案,各个点到后面其他点的方案
void Tuopu(int *F,bool type)//DAG上dp算方案数
{
    queue <int> q;
    for(int i=1;i<=n;i++) if(!du[i]) q.push(i),F[i]=1;
    if(type) for(int i=1;i<=n;i++) F[i]=1;
    while(!q.empty())
    {
        int x=q.front(),len=V[x].size(); q.pop();
        for(int i=0;i<len;i++)
        {
            int &v=V[x][i]; F[v]=fk(F[v]+F[x]);
            du[v]--; if(!du[v]) q.push(v);
        }
    }
}
void calc(int S)//计算以S为起点的贡献
{
    for(int i=1;i<=n;i++)
        du[i]=f[i]=g[i]=0,V[i].clear(),G[i].clear();
    for(int i=1;i<=n;i++)
        for(int j=fir[i];j;j=from[j])
        {
            int &v=to[j]; if(dis[v]!=dis[i]+val[j]) continue;
            V[i].push_back(v); du[v]++;
        }
    Tuopu(f,0);
    for(int i=1;i<=n;i++) G[i]=V[i],V[i].clear();
    for(int i=1;i<=n;i++)
        for(int j=G[i].size()-1;j>=0;j--) V[G[i][j]].push_back(i),du[i]++;//建反图
    Tuopu(g,1);
    for(int i=1;i<=n;i++)
        for(int j=fir[i];j;j=from[j])
        {
            int &v=to[j]; if(dis[v]!=dis[i]+val[j]) continue;
            ans[id[j]]=fk(ans[id[j]] + 1ll*f[i]*g[v]%mo );//注意long long
        }
}
int main()
{
    n=read(),m=read(); int a,b,c;
    for(int i=1;i<=m;i++)
        a=read(),b=read(),c=read(),add(a,b,c,i);
    for(int i=1;i<=n;i++) Dijk(i),calc(i);
    for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
    return 0;
}

 

转载于:https://www.cnblogs.com/LLTYYC/p/11485829.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值