挑战程序设计竞赛(第二章:2.5 图论)

RoadBlocks

参考博文:挑战程序设计竞赛: Roadblocks
参考博文:【dijkstra优化/次短路径】POJ3255-Roadblocks

  • 思路:dijstra算法的变体。
    • 这道题的做法和最短路径基本一致,唯一的不同点在于,在求出最短路径的情况下必须要保留下次短路径。对于Dijkstra判断中取出的每一个点,如果到它的最短距离大于当前该点的次短距离,则当前该点已经取到最短距离和次短距离,不进行操作,否则进行两次判断:如果小于最短边,则赋给最短变,并将最短边赋给次短边;或者如果大于最短变且小于次短边,则赋给次短边。两次完成之后均要加入队列。
    • 并且没有使用vis数组来标记已经求出最短路径的结点,因为还需要多次使用该结点求次短路径,因此需要遍历所有边。

代码:

#include <iostream>
#include <queue>
#include <vector>
#include <cstring>
#include <algorithm>
#include <cstdio>
using namespace std;

/*
    不使用vis来标记是否访问过,是因为可以访问多次来取次短路径
*/
const int MAX = 50005;
const int INF = 1<<29;
struct Node
{
    int v, c;//编号和最短路径距离
    Node(int v=0, int c=0): v(v), c(c){}

    bool operator < (const Node& a) const{
        return c>a.c;
    }
};
int d[MAX], d2[MAX];//最短路和次短路
vector<Node> G[MAX];//邻接表
priority_queue<Node> pq;
Node pn;
int N, R, A, B, D;

void dijstra(int start)
{
    //初始化
    fill(d, d+N+1, INF);
    fill(d2, d2+N+1, INF);
    d[start] = 0;

    pq.push(Node(start, d[start]));

    while(!pq.empty())
    {
        pn = pq.top(); pq.pop();
        int u = pn.v, dis = pn.c;

        if(d2[u]<dis) continue;//如果次短路径都小于dis 那么就不用再继续去更新
        for(int j=0; j<G[u].size(); j++)//遍历邻接表
        {
            Node n = G[u][j];
            int dis2 = dis+n.c, v = n.v;

            //更新最短路
            if(d[v]>dis2)
            {
                swap(d[v], dis2);
                pq.push(Node(v, d[v]));
            }
            if(d2[v]>dis2 && d[v]<dis2)
            {
                d2[v] = dis2;
                pq.push(Node(v, d2[v]));
            }
        }
    }
    printf("%d\n", d2[N]);
}

int main()
{
    while(scanf("%d%d", &N, &R)!=EOF)
    {
        for(int i=0; i<R; i++)
        {
            cin >> A >> B >> D;
            G[A].push_back(Node(B, D));
            G[B].push_back(Node(A, D));
        }
        dijstra(1);
    }
    return 0;
}
Conscription

参考博文:挑战程序设计竞赛:Conscription
题目大意:选择有N个女兵和M个男兵,每选一个花费10000,男女兵之间有关系d,可以将花费降低d,问按什么顺序选择花费最少。

  • 思路:可以转换为求解最小生成树(MST),根据题意知使用Kruskal方法比较好(使用并查集)。注意在找出MST后,计算其花费后,需要统计有多少个连通子图(单点—与其他点没有任何联系,也可以看做一个连通子图),然后加上其总数*10000。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;

const int MAX = 50005;
int N, M, R, T, x, y, d;
struct Edge
{
    int from, to, value;
    bool operator < (const Edge &A) const
    {
        return value < A.value;
    }
};
Edge G[MAX];

int pre[MAX], Rank[MAX], vis[MAX];
void Init(int n)
{
    for(int i=0; i<=n; i++)
    {
        pre[i] = i;
        Rank[MAX] = 0;
    }
}

int findRoot(int x)
{
    int r = x;
    while(r!=pre[r]) r = pre[r];

    int j = x, i;
    while(j!=pre[j])
    {
        i = pre[j];
        pre[j] = r;
        j = i;
    }
    return r;
}

void join(int x, int y)
{
    int fx = findRoot(x), fy = findRoot(y);
    if(Rank[fx]>Rank[fy])
        pre[fy] = fx;
    else
    {
        pre[fx] = fy;
        if(Rank[fx]==Rank[fy]) Rank[fy]++;
    }
}

bool same(int x, int y)
{
    return findRoot(x)==findRoot(y);
}

void solve()
{
    int ans = 0;
    for(int i=0; i<R; i++)
    {
        if(!same(G[i].from, G[i].to))
        {
            ans += G[i].value;
            join(G[i].from, G[i].to);
        }
    }
    //找出有多少个连通图(包括单点)
    for(int i=0; i<N+M; i++)
    {
        if(pre[i]==i)
        {
            ans += 10000;
        }
    }
    printf("%d\n", ans);
}

int main()
{
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d%d%d", &N, &M, &R);
        Init(N+M);
        for(int i=0; i<R; i++)
        {
            scanf("%d%d%d", &x, &y, &d);
            G[i].from = x, G[i].to = y+N, G[i].value = 10000-d;
        }
        sort(G, G+R);
        solve();
    }
    return 0;
}
Layout

题目链接Layout
参考博文:Layout POJ NO.3169
Layout POJ - 3169 (差分约束+最短路)
挑战程序设计竞赛: Layout讲解了为什么将不等式转换为图结构。
思路:重点是建图思想,并将问题转换为求带负环的最短路问题。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

typedef long long LL;
const int MAX = 100000;
const int INF = 1<<29;
struct Edge
{
    int from, to, cost;
};
int N, ML, MD, A, B, D, E;
Edge e[MAX];
LL d[MAX];

//找到各个结点的最短路径,并判断是否存在负环
bool Bellman_Ford()
{
    fill(d, d+MAX, INF);
    d[1] = 0;
    int k = 0;
    while(1)
    {
        bool updated = false;
        for(int i=0; i<E; i++)//遍历所有的边
        {
            Edge w = e[i];
            if(d[w.from]!=INF && d[w.to]>d[w.from]+w.cost)
            {
                d[w.to] = d[w.from]+w.cost;
                updated = true;
            }
        }
        if(!updated) break;//没有更新则立即停止
        k++;
        if(k>=N) return false;//判断是否有负环
    }
    return true;
}
//单纯判断是否存在负环
bool find_negative_loop()
{
    //memset(d, 0, sizeof(d));
    fill(d, d+MAX, INF);
    d[1] = 0;
    for(int i=1; i<=N; i++)
    {
        for(int j=0; j<E; j++)
        {
            Edge w = e[j];
            if(d[w.to]>d[w.from]+w.cost)
            {
                d[w.to]=d[w.from]+w.cost;
                if(i==N) return true;//第N次仍然更新
            }
        }
    }
    return false;
}

int main()
{
    E = 0;
    scanf("%d%d%d", &N, &ML, &MD);
    for(int i=0; i<ML; i++)
    {
        scanf("%d%d%d", &A, &B, &D);
        e[E].from = A, e[E].to = B, e[E++].cost = D;
    }
    for(int i=0; i<MD; i++)
    {
        scanf("%d%d%d", &A, &B, &D);
        e[E].from = B, e[E].to = A, e[E++].cost = -D;
    }
    /*
    if(find_negative_loop())
    {
        printf("-1\n");
        return 0;
    }*/
    if(!Bellman_Ford())
        printf("-1\n");
    else if(d[N]==INF)
        printf("-2\n");
    else
        printf("%lld\n", d[N]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值