EOJ-1848 你是ACM吗?

来源:互联网 发布:伊甸园ydy论坛广州域名 编辑:程序博客网 时间:2024/06/05 10:02

Description

SH 市有N个运输中转点(简单标示为 1,2,3,....,N),中转点之间可能有一条运输线路,这条线路有一个特殊的地方就是从A 到B点需要耗费 c1 个单位的查克拉(SH市的货币单位),但从B到A可能需要 c2 个查克拉。当然c1不一定等于c2,也能从A到B之后就不能从B返回A了。你可以理解为这些线路是“单向”的。线路总共有 M 条。每天有N-1辆车从KOP集团总部(这里假设就是标号为1的中转点),出发,分别发往N-1个剩下的中转点,然后当天再从所到达的中转点返回。你的任务就是要求出一天的最小耗费。

Input

第一行为 C ,表示有C个测试用列。接下来分别为C个测试用列。
每个测试用例的格式如下:
第一行为两个整数,N,M ,分别表示有N个中转点和M条道路。( 1=< N, M <=1000000 .);
紧接着的M 行每行有三个值 A B c; 分别表示从中转点A到中转点B需要耗费 c 个单位的查克拉。 ( 0<= c <= 1000000000 ).

Output

你的输出应该包括C行,每行输出一个值,对应于相应的用列的最少耗费。

Sample Input

2
2 2
1 2 13
2 1 33
4 6
1 2 10
2 1 60
1 3 20
3 4 10
2 4 5
4 1 50

Sample Output

46
210


题目分析:建立有向图,出发时求单源最短路径再累加结果,返回时相当于对每一个顶点都使用一次单源最短路径,一共V-1次,再累加结果,两次结果相加得到最终答案。但是这道题目给的数据十分庞大,所以这样做的话肯定会超时的,所以要想办法优化。


方法1.Dijsktra+最小优先队列:出发时使用Dijkstra求一次单源最短路径;返回时把路径交换一下,然后问题等价于出发时的情况。例如u--->v:10, v--->u:20; 那么出发时直接从出发点求一次单源最短路径,返回时把u到v置为20,v到u置为10,然后再从出发点求一次单源最短路径。


#include <cstdio>#include <cstring>#include <iostream>#include <vector>#include <queue>using namespace std;#define MAXN 1000005typedef long long LL;const int INF = 0x3f3f3f3f;int n, m;//n个顶点 m条边LL dis[MAXN];struct node{    int v;    long long w;   //v代表顶点,w代表权重    node () {}         node (int vv, long long ww) : v(vv), w(ww) {}    bool operator < (const node& x) const    {        return w > x.w;    }}; vector<node> edge[2][MAXN];//保存两张图 void dijkstra(int k){    for(int i = 1; i <= n; ++i) dis[i] = INF;//初始化为无穷大    dis[1] = 0;//起始点距离设为0    priority_queue<node> q;//最小优先队列    q.push(node(1, 0));//起始点入队    node now, next;    while (!q.empty())    {        now = q.top(), q.pop();//出队        for(int i = 0; i < edge[k][now.v].size(); ++i)        {            next = edge[k][now.v][i];//遍历每一个与之关联的点            if (dis[next.v] > now.w + next.w)            {                dis[next.v] = now.w + next.w;//更改最短距离                q.push(node(next.v, dis[next.v]));//入队            }        }    }} int main(){    int T;    scanf("%d", &T);    while (T--)    {        scanf("%d%d", &n, &m);        for(int i = 0; i < 2; ++i)            for(int j = 1; j <= n; ++j)                edge[i][j].clear();        while (m--)        {            int u, v;             LL w;            scanf("%d%d%I64d", &u, &v, &w);            edge[0][u].push_back(node(v, w));//处理出发情况的最小值            edge[1][v].push_back(node(u, w));//处理返回情况的最小值        }        LL ans = 0;        dijkstra(0);//出发        for(int i = 1; i <= n; ++i) ans += dis[i];        dijkstra(1);//返回        for(int i = 1; i <= n; ++i) ans += dis[i];        printf("%I64d\n", ans);    }        return 0;}


方法2.SPFA:出发与返回时的处理与方法一相同。


#include <iostream>#include <cstdio>#include <queue>#include <vector>using namespace std;#define MAXN 1000005const int INF=0x3f3f3f3f;struct Edge{    int v;//u-->v    int w;//代价    Edge(){}    Edge(int vv, int ww):v(vv), w(ww){}};vector<Edge> edge[2][MAXN];long long dis[MAXN];int vis[MAXN];int n, m;//n个顶点、m条边void SPFA(int k){    for(int i = 1; i <= n; i++)        dis[i] = INF, vis[i] = 0;//初始化    queue<int> q;    q.push(1);//入队源点    dis[1] = 0;    vis[1] = 1;//表示该顶点在队列中    while(!q.empty())    {        int now, next;        now = q.front();        q.pop();        vis[now] = 0;//表示顶点出队        for(unsigned int i = 0; i < edge[k][now].size(); i++)        {            next = edge[k][now][i].v;            if(dis[next] > dis[now] + edge[k][now][i].w)//松弛操作            {                dis[next] = dis[now] + edge[k][now][i].w;                if(!vis[i])//不在队列中则入队                    q.push(next), vis[next] = 1;            }        }    }}int main(){    int t;    int u, v, cost;    scanf("%d", &t);    while(t--)    {        scanf("%d%d", &n, &m);        for(int i = 0; i < 2; ++i)            for(int j = 1; j <= n; ++j)                edge[i][j].clear();        while(m--)        {            scanf("%d%d%d", &u, &v, &cost);            edge[0][u].push_back(Edge(v, cost));            edge[1][v].push_back(Edge(u, cost));        }        long long sum = 0;        SPFA(0);        for(int i = 1; i <= n; i++)            sum += dis[i];        SPFA(1);        for(int i = 1; i <= n; i++)            sum += dis[i];        printf("%I64d\n", sum);    }    return 0;}

ps:SPFA可用来判断图中是否带负环,单这道题目中不涉及负环,因此和SPFA本身的算法相比少了一些步骤,若需要判断负环,则还需一个计数数组cnt[i],表示第i个顶点入队的次数,如果某个顶点入队超过V次,则说明该图中有负环存在,没有最短路径。




SPFA算法参考:http://www.cnblogs.com/scau20110726/archive/2012/11/18/2776124.html




1 0
原创粉丝点击