1. 程式人生 > >POJ1511 Invitation Cards

POJ1511 Invitation Cards

題意:

n個點,m條有向帶權邊,權值為正表示花費。設從節點1到其他所有點的總花費為A,從其他所有點到節點1的總花費為B。不同點之間通行的花費將重複計數(即:假設x到y的路徑與x到z的路徑經過同一條邊,這條邊的花費會被計數兩次,具體看樣例體會)。問A+B的最小值是多少?

分析:

從節點1到其他所有點即為求1到其他所有點的最短路徑之和。無負權邊則可用dijkstra演算法,資料量比較大可考慮用優先佇列優化。從其他各點回來的時候,我們將圖各邊反向,這等價於求反向後的圖中1到其他各點的最短路徑之和。這裡我用前向星法存邊,具體的反向操作看程式碼。

時限8000ms,以下程式碼25052K,2204ms過。

程式碼:

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
typedef __int64 ll;
#define N 1000005
#define INF (1<<30)
struct Edge{
    int to,w,next;
};
Edge edge[N];bool vis[N];ll ans;
int head[N],cnt,dist[N],n,m,head2[N];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;
void addedge(int u,int v,int w){
    edge[cnt].to=v;edge[cnt].w=w;edge[cnt].next=head[u];head[u]=cnt++;
}
void dijkstra(){
    int u,v;
    while(!q.empty()) q.pop();
    for(int i=1;i<=n;++i){dist[i]=INF;vis[i]=false;}
    dist[1]=0;q.push(make_pair(0,1));
    while(!q.empty()){
        u=q.top().second;q.pop();
        if(vis[u]) continue;
        vis[u]=true;ans+=(ll)dist[u];
        for(int i=head[u];i!=-1;i=edge[i].next){
            v=edge[i].to;
            if(!vis[v]&&dist[v]>dist[u]+edge[i].w){
                dist[v]=dist[u]+edge[i].w;
                q.push(make_pair(dist[v],v));
            }
        }
    }
}
void rev(){
    int u,v;
    for(int i=1;i<=n;++i){head2[i]=head[i];head[i]=-1;}
    for(int i=1;i<=n;++i){
        for(int j=head2[i];j!=-1;){
            u=j;v=edge[j].to;
            j=edge[j].next;edge[u].to=i;
            edge[u].next=head[v];head[v]=u;
        }
    }
}
int main(){
    int t,u,v,w;scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&m);cnt=0;
        for(int i=1;i<=n;++i) head[i]=-1;
        for(int i=0;i<m;++i){
            scanf("%d%d%d",&u,&v,&w);
            addedge(u,v,w);
        }ans=0;
        dijkstra();rev();dijkstra();
        printf("%I64d\n",ans);
    }
    return 0;
}