poj初期图算法

本文详细介绍了ACM初期的图算法,包括深度优先遍历、广度优先遍历、最短路径算法、最小生成树算法、拓扑排序和二分图最大匹配。通过对一系列POJ和CF题目进行解析,阐述了这些算法在实际问题中的应用,并提供了典型例题和解题思路。
摘要由CSDN通过智能技术生成

 

第二个专题了,初期图算法:

 

(1)、图的深度优先遍历和广度优先遍历

感觉这题比较好,就找了这题….

1CF659E - New Reform

题意:给定一个有n个顶点,m条边的无向边,现要为每条边指定一个方向,使得separate cities 越少越好,一个城市如果如果他的入度为0的话,则其为separate cities 。

分析:分析下可知,如果一个图为树的话,那么任意规定一个点为根节点,遍历一次所有点方向即确定了,此时分离的城市数量为1,如果一个图存在环的话,那么此时分离城市的数量为0,此时选取环上一个与环外有边相连的点为根节点遍历图即可,此时任意点入度都大于等于1,所以数量为0,所以对于每一个连通分支,求得数量相加即可。

最基本的图的遍历,分为两种方式,dfs与bfs,此题运用到了dfs的思想,每次确定一个点与其邻接的点的方向即可。

 

#include <bits/stdc++.h>
using namespace std;

const int N = 100010, M = 100010;
int vis[N], head[N];
int cnt, flag, k;
struct edge{
    int v, next;
    edge(int v, int n) : v(v), next(n) {}
    edge() {}
}es[M<<1];
void add(int u, int v) { es[k] = edge(v, head[u]), head[u] = k++;}

void dfs(int x, int pre) {
    vis[x] = cnt;
    for (int i = head[x]; i != -1; i = es[i].next) {
        int v = es[i].v;
        if (vis[v] == cnt && v != pre) flag = 0;
        if (!vis[v]) dfs(v, x);
    }
}
int main() {
    int n, m;
    scanf("%d %d", &n, &m);
    memset(head+1, -1, 4*n);
    for (int i = 0; i < m; i++) {
        int u, v;
        scanf("%d %d", &u, &v);
        add(u, v); add(v, u);
    }
    int ans = 0;
    for (int i = 1; i <= n; i++)
        if (!vis[i]) {
            flag = 1; ++cnt;
            dfs(i, i); ans += flag;
        }
    printf("%d\n", ans);
    return 0;
}

 

(2)、最短路径算法

1、poj1860

题意:给定N种货币,某些货币之间可以相互兑换,现在给定一些兑换规则,问能否从某一种货币开始兑换,经过一些中间货币之后,最后兑换回这种货币,并且得到的钱比之前的多。

分析:明显的判环,利用spfa即可,如果一个点入队超过n次,那么则存在环。

 

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

const int N = 110;
struct currency {
    int t;
    double r, c;
    currency(int t = 0, double r = 0, double c = 0) : t(t), r(r), c(c) {}
};
vector<currency> G[N];
double d[N];
int len, s, n, m;
queue<int> q;
int vis[N], num[N];

bool find_loop() {
    q.push(s); num[s]++;
    while (!q.empty()) {
        int x = q.front(); q.pop();
        vis[x] = 0;
        for (int i = 0; i < G[x].size(); i++) {
            currency e = G[x][i];
            double t = (d[x] - e.c) * e.r;
            if (t > d[e.t]) {
                d[e.t] = t;
                if (!vis[e.t]) {
                    vis[e.t] = 1; q.push(e.t);
                    if (++num[e.t] > n) return 1;
                }
            }
        }
    }
    return 0;
}

int main() {
    double t;
    scanf("%d %d %d %lf", &n, &m, &s, &t);
    d[s] = t;
    for (int i = 0; i < m; i++) {
        int f, t;
        double rft, cft, rtf, ctf;
        scanf("%d %d %lf %lf %lf %lf", &f, &t, &rft, &cft, &rtf, &ctf);
        G[f].push_back(currency(t, rft, cft));
        G[t].push_back(currency(f, rtf, ctf));
    }
    puts(find_loop() ? "YES" : "NO");
    return 0;
}


2、poj3259

题意:虫洞问题,现在有n个点,m条边,代表现在可以走的通路,比如从a到b和从b到a需要花费c时间,现在在地上出现了w个虫洞,虫洞的意义就是你从a到b花费的时间是-c(时间倒流,并且虫洞是单向的),现在问你从某个点开始走,能否回到从前。

分析:上题判正环,这题建图后判负环。

 

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

const int N = 510, M = 5210;
const int INF = 0x3f3f3f3f;
int vis[N], head[N], cnt[N], d[N];
int k;
struct edge{
    int v, d, next;
    edge(int v, int d, int n) : v(v), d(d), next(n) {}
    edge() {}
}es[M<<1];
void add(int u, int v, int d) {
    es[k] = edge(v, d, head[u]);
    head[u] = k++;
}
queue<int> q;
void init(int n) {
    k = 0;
    memset(head+1, -1, 4*(n+5));
    memset(cnt+1, 0, 4*(n+5));
    memset(vis+1, 0, 4*(n+5));
    memset(d+1, INF, 4*(n+5));
    while (!q.empty()) q.pop();
}
bool solve(int s, int n) {
    d[s] = 0; cnt[s]++;
    q.push(s);
    while (!q.empty()) {
        int t = q.front(); q.pop();
        vis[t] = 0;
        for (int i = head[t]; i != -1; i = es[i].next) {
            int v = es[i].v;
            if (d[v] > d[t] + es[i].d) {
                d[v] =  d[t] + es[i].d;
                if (!vis[v]) {
                    vis[v] = 1; q.push(v);
                    if (++cnt[v] > n) return 1;
                }
            }
        }
    }
    return 0;
}
int main() {
    int f, n, m, w, s;
    scanf("%d", &f);
    while (f--) {
        scanf("%d %d %d", &n, &m, &w);
        init(n);
        int u, v, d;
        for (int i = 0; i < m; i++) {
            scanf("%d %d %d", &u, &v, &d);
            add(u, v, d); add(v, u, d);
        }
        for (int i = 0; i < w; i++) {
            scanf("%d %d %d", &u, &v, &d);
            add(u, v, -d);
            s = u;
        }
        puts(solve(s, n) ? "YES" : "NO");
    }
    return 0;
}


3、poj1062

题意:有N个物品,每个物品都有自己的价格,但同时某些物品也可以由其他的(可能不止一个)替代品,这些替代品的价格比较“优惠”,问怎么样选取可以让你的花费最少来购买到物品1。

分析:很明显是要求最短路,求自己到1号物品的最短路,不能直接利用dijstra求解,但是我们可以枚举最小等级,这样再利用dijstra求解,等级差超过m的点不考虑。这样找出最小的值就行了。但是这样感觉有点麻烦了,不如直接搜索。。。

但是,有个问题就是数据量不能太大,否则的话队列中加入的点太多,会超内存,即使题目中的这点数据量,把队列放到函数中都会错,可能栈溢出了吧,只能用全局的。。。

 

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

const int N = 105;
struct edge{
    int to, dis;
    edge(int a = 0, int d = 0) : to(a), dis(d){}
};
struct node{
    int id, d, mag, mig;
    node(int i = 0, int d = 0, int n = 0, int m = 0) : id(i), d(d), mag(n), mig(m) {}
    bool operator < (const node& x)const { return d > x.d; }
};
vector<edge> G[N];
int d[N], grade[N];
int n, m;
priority_queue<node> q;

int dijstra() {
    for (int i = 1; i <= n; i++) q.push(node(i, d[i], grade[i], grade[i]));
    while (!q.empty()) {
        node t = q.top(); q.pop();
        int x = t.id;
        //printf("%d %d %d %d\n", x, t.d, t.mag, t.mig);
        if (x == 1) return t.d;
        for (int i = 0; i < G[x].size(); i++) {
            edge e = G[x][i];
            int a = t.mag, b = t.mig;
            if (abs(grade[e.to] - a) > m || abs(grade[e.to] - b) > m) continue;
            q.push(node(e.to, e.dis + t.d, max(a, grade[e.to]), min(b, grade[e.to])));
        }
    }
}
int main() {
    scanf("%d %d", &m, &n);
    for (int i = 1; i <= n; i++) {
        int x;
        scanf("%d %d %d", &d[i], &grade[i], &x);
        for (int j = 0; j < x; j+&
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值