ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Day 5 上午

2019-05-02 11:53:31  阅读:275  来源: 互联网

标签:edg int Queue edge 上午 visited include Day


内容提要

图论QwQ


 

学好图论的基础:
必须意识到图论 hendanteng
xuehuifangqi(雾


G = (V; E)
一般来说,图的存储难度主要在记录边的信息
无向图的存储中,只需要将一条无向边拆成两条即可
邻接矩阵:用一个二维数组 edg[N][N] 表示
edg[i][j] 就对应由 i 到 j 的边信息
edg[i][j] 可以记录 Bool,也可以记录边权
缺点:如果有重边有时候不好处理
空间复杂度 O(V^2)
点度等额外信息也是很好维护的

#include <bits/stdc++.h>

using namespace std;

const int N = 5005;

int ideg[N], odeg[N], n, m, edg[N][N];
bool visited[N];

void travel(int u, int distance)
{
    cout << u << " " << distance << endl; visited[u] = true;
    for (int v = 1; v <= n; v++)
        if (edg[u][v] != -1 && !visited[v])//是否已经访问过 
            travel(v, distance + edg[u][v]); //if there is an edge (u, v) and v has not been visited, then travel(v)
}
int main()
{
    cin >> n >> m;
    memset(edg, -1, sizeof edg);
    memset(visited, false, sizeof visited);
    for (int u, v, w, i = 1; i <= m; i++)
        cin >> u >> v >> w, edg[u][v] = w, odeg[u]++, ideg[v]++;//出度和入度 
    for (int i = 1; i <= n; i++)
        cout << ideg[i] << " " << odeg[i] << endl;
    for (int i = 1; i <= n; i++)
        if (!visited[i]) travel(i, 0);
}

/*
Given a graph with N nodes and M unidirectional edges.
Each edge e_i starts from u_i to v_i and weights w_i
Output a travelsal from node 1 and output degree of each node.
*/

 

邻接表(链式前向星)
对每一个点 u 记录一个 List[u],包含所有从 u 出发的边
直接用数组实现 List[u]?读入边之前不知道 List[u] 长度
手写链表
用 STL 中的 vector 实现变长数组
只需要 O(V + E) 的空间就能实现图的存储

指针版本(一般不用)

#include <bits/stdc++.h>

using namespace std;

const int N = 5005;

struct edge {
    int u, v, w; edge *next;//next指针指向 
    edge(int _u, int _v, int _w, edge *_next):
        u(_u), v(_v), w(_w), next(_next) {}
};
edge *head[N]; //List[u] 最前面的节点是谁 
int ideg[N], odeg[N], n, m;
bool visited[N];

void add(int u, int v, int w)
{
    edge *e = new edge(u, v, w, head[u]);
    head[u] = e;
}
void travel(int u, int distance)
{
    cout << u << " " << distance << endl; visited[u] = true;
    for (edge *e = head[u]; e ; e = e -> next)
        if (!visited[e -> v])
            travel(e -> v, distance + e -> w); //if there is an edge (u, v) and v has not been visited, then travel(v)
}
int main()
{
    cin >> n >> m;
    memset(visited, false, sizeof visited);
    memset(head, 0, sizeof head);
    for (int u, v, w, i = 1; i <= m; i++)
        cin >> u >> v >> w, add(u, v, w), odeg[u]++, ideg[v]++;
    for (int i = 1; i <= n; i++)
        cout << ideg[i] << " " << odeg[i] << endl;
    for (int i = 1; i <= n; i++)
        if (!visited[i]) travel(i, 0);
}

/*
Given a graph with N nodes and M unidirectional edges.
Each edge e_i starts from u_i to v_i and weights w_i
Output a travelsal from node 1 and output degree of each node.
*/

 

数组模拟版本:

#include <bits/stdc++.h>

using namespace std;

const int N = 5005;

struct edge {
    int u, v, w, next;
}edg[N];
int head[N]; //List[u] stores all edges start from u
int ideg[N], odeg[N], n, m, cnt; //cnt: numbers of edges
bool visited[N];

void add(int u, int v, int w)
{
    int e = ++cnt;
    edg[e] = (edge){u, v, w, head[u]};
    head[u] = e;
}
void travel(int u, int distance)
{
    cout << u << " " << distance << endl; visited[u] = true;
    for (int e = head[u]; e ; e = edg[e].next)
        if (!visited[edg[e].v])
            travel(edg[e].v, distance + edg[e].w); //if there is an edge (u, v) and v has not been visited, then travel(v)
}
int main()
{
    cin >> n >> m; cnt = 0;
    memset(visited, false, sizeof visited);
    memset(head, 0, sizeof head);
    for (int u, v, w, i = 1; i <= m; i++)
        cin >> u >> v >> w, add(u, v, w), odeg[u]++, ideg[v]++;
    for (int i = 1; i <= n; i++)
        cout << ideg[i] << " " << odeg[i] << endl;
    for (int i = 1; i <= n; i++)
        if (!visited[i]) travel(i, 0);
}

/*
Given a graph with N nodes and M unidirectional edges.
Each edge e_i starts from u_i to v_i and weights w_i
Output a travelsal from node 1 and output degree of each node.
*/

 

用edg[u][i]表示从u出发第i条信息,但这样还是n^2
但这样很浪费,因为数组不能全部利用
传统的方法要在定义数组是就规定他的大小,但是我们读入之前并不知道从每个点出发的边数
我们可以用vector
一些 vector 的细节
vector 本质就是 c++ 模板库帮我们实现好的变长数组
向一个数组 a 的末尾加入一个元素 x a:push_back(x)
询问数组 a 的长度 a.size()
注意: vector 中元素下标从 0 开始

#include <bits/stdc++.h>

using namespace std;

const int N = 5005;

struct edge {
    int u, v, w;
};
vector<edge> edg[N]; //edge记录变长数组记录的是什么类型 
int ideg[N], odeg[N], n, m, cnt; //cnt: numbers of edges
bool visited[N];

void add(int u, int v, int w)
{
    edg[u].push_back((edge){u, v, w});//一个强制类型转换 
}
void travel(int u, int distance)
{
    cout << u << " " << distance << endl; visited[u] = true;
    for (int e = 0; e < edg[u].size(); e++)//遍历边 
        if (!visited[edg[u][e].v])//以u出发的第e条出边 
            travel(edg[u][e].v, distance + edg[u][e].w); //if there is an edge (u, v) and v has not been visited, then travel(v)
}
int main()
{
    cin >> n >> m; cnt = 0;
    memset(visited, false, sizeof visited);
    for (int u, v, w, i = 1; i <= m; i++)
        cin >> u >> v >> w, add(u, v, w), odeg[u]++, ideg[v]++;
    for (int i = 1; i <= n; i++)
        cout << ideg[i] << " " << odeg[i] << endl;
    for (int i = 1; i <= n; i++)
        if (!visited[i]) travel(i, 0);
}

/*
Given a graph with N nodes and M unidirectional edges.
Each edge e_i starts from u_i to v_i and weights w_i
Output a travelsal from node 1 and output degree of each node.
*/

生成树
给定一个连通无向图 G = (V; E)
E′ ⊂ E
G′ = (V; E′) 构成一棵树
G′ 就是 G 的一个生成树

生成树不是唯一的,并且数量是指数级别

问题描述
给定一个 n 个点 m 条边的带权无向图,求一个生成树,使得生成树中最大边权的最小
数据范围: n; m ≤ 10^6
Algorithms for Minimal Spanning Tree
Kruskal
Prim
Kosaraju


并查集
生成树的本质:选取若干条边使得任意两点连通
维护图中任意两点的连通性
查询任意两点连通性
添加一条边,使两个端点所在连通块合并

Kruskal
Intuitive ideas
将原图无向边按照边权从小到大排序
找到当前边权最小的边 e : (u; v)
如果 u 和 v 已经连通,则直接删除这条边(因为形成了环)
如果 u 和 v 已经未连通,将之加入生成树
重复上述过程
证明:
Rigorous proof
消圈算法:
如果原图中有一个环,然后把这个圈中边权最大的去掉,这样不断去掉最后得到的就是最小生成树
但是由于环不好找,所以不用他

#include <bits/stdc++.h>

using namespace std;

const int maxn = 1000005;
struct edge {
    int u, v, w;
}edg[maxn];
int n, m, p[maxn], ans = 0;

bool cmp(edge a, edge b)
    {return a.w < b.w;}
int findp(int t) 
    {return p[t] ? p[t] = findp(p[t]) : t;}
bool merge(int u, int v)
{
    u = findp(u); v = findp(v);
    if (u == v) return false;
    p[u] = v; return true;
}
int main()
{
    cin >> n >> m;
    for (int i = 1, u, v, w; i <= m; i++)
        cin >> u >> v >> w, edg[i] = (edge){u, v, w};
    sort(edg + 1, edg + m + 1, cmp);
    
    for (int i = 1; i <= m; i++)
        if (merge(edg[i].u, edg[i].v))
            ans = max(ans, edg[i]. w);
    cout << ans << endl;
}

本蒟蒻的博客:传送门


路径
P = p0; ...; pn 为 u 到 v 的路径
p0 = u, pn = v
对于任意i∈2 [1; n]; 存在e : (pi−1; pi)∈E
P 的长度
length(P) = ∑e∈P length(e)

我们一般考虑简单路径,就是不重复经过一个点的路径
显然它也不一定是惟一的

最短路径问题
给定一个有向图 G,询问 u 到 v 之间最短路径长度
记 d(u,v) 表示 u 到 v 的最短路径长度
为方便起见,不妨规定 u 和 v 不连通时, d(u; v) = +1
Algorithms for Shortest Path Problem
floyd
Bellman-Ford
SPFA
Dijkstra

先介绍一下松弛操作
SSP 算法的本质思想就是不断进行松弛操作
d(u; v) ≤ d(u; w) + d(w; v)
我们算出来了一个d(u,v)但是他不一定是最终的d(u,v),这时如果我们找到了d(u,w) + d(w,v)<当前的d(u,v),就更新d(u,v)的值

floyd
初始时, d(u; v) 就等于 u 到 v 的边权
用邻接矩阵的形式记录 d
u 和 v 没边, d(u,v) = +∞
u 和 v 有重边,保留最小边权
三层循环枚举 k; i; j,执行松弛操作
d(i,j) = min{d(i,j); d(i,k) + d(k,j)}

代码:

#include <bits/stdc++.h>

using namespace std;

const int N = 505;
const int inf = 1 << 29;

int d[N][N], n, m;
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++) d[i][j] = inf;
    for(int i = 1;i <= n; i++)
        d[i][i]=0; 
    for (int u, v, w, i = 1; i <= m; i++)
        cin >> u >> v >> w, d[u][v] = min(d[u][v], w);

    for (int k = 1; k <= n; k++)
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
    //必须先枚举中间点 
}

 

本蒟蒻博客:传送门

 

三层循环枚举 k,i,j,执行松弛操作
d(i,j) = min{d(i,j), d(i,k) + d(k,j)}
任意两点之间的最短路最多经过 n − 1 条有向边
k = 1 的循环结束之后
如果 u --> v 的 最短路径 只经过 1,此时的 d(u,v) 就是真实值
k = 2 的循环结束之后
如果 u --> v 的 最短路径 只经过 1,2,此时的 d(u,v) 就是真实值
k = t 的循环结束之后
如果 u --> v 的 最短路径 只经过 1...t,此时的 d(u,v) 就是真实值
不断有真实值被算出,然后又不断合并

不断枚举松弛操作的中间点 k
算法时间复杂度 O(n^3)
优势:处理出 d 之后,任意两点 SP 能 O(1) 询问
注意:必须先枚举中间点!!!必须先枚举中间点!!!必须先枚举中间点!!!

负权环
如果存在一个环,其边权和为负数,则称为负权环
u --> v 存在一个负权环,d(u,v) = −∞
判断图是否含负权环
floyd 后检查是否存在 d(u,u) < 0,如果存在就说明有负权环


单源最短路
不需要知道图中任意两个点的 SP
只需要知道某一点 u 到其它点的 SP
u 称为源点,单源最短路就是求从 u 出发的最短路
为方便,下面记源点为 S

Bellman-Ford
将 d(S,u) 简写为 d(u),初始 d(S) = 0, d(u) = +∞
执行 n 次全局松弛操作
枚举图中每条边 e : (u,v,w)
松弛 d(v) = min{d(v),d(u) + w}

证明:
如果目前存在 d(u) 还不是真实值,一定有边能继续松弛
任意最短路经过不超过 n − 1 条边, n - 1 次松弛足矣

优势:算法很直观
算法时间复杂度 O(nm)
判负权环
再多进行一次全局松弛,如果有 d 被更新,一定有负权环

#include <bits/stdc++.h>

using namespace std;

const int N = 505;
const int inf = 1 << 29;

int d[N][N], n, m;
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++) d[i][j] = inf;
    for(int i = 1;i <= n; i++)
        d[i][i]=0; 
    for (int u, v, w, i = 1; i <= m; i++)
        cin >> u >> v >> w, d[u][v] = min(d[u][v], w);

    for (int k = 1; k <= n; k++)
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
    //必须先枚举中间点 
}

 

如果有一次松弛没有任何边被改变,就说明已经结束了
所以可以做一下小小的优化(常数级别)

 

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<string>
#include<algorithm>
#include<time.h>
#include<queue>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pr;
const double pi=acos(-1);
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define Rep(i,u) for(int i=head[u];i;i=Next[i])
#define clr(a) memset(a,0,sizeof a)
#define pb push_back
#define mp make_pair
#define fi first
#define sc second
ld eps=1e-9;
ll pp=1000000007;
ll mo(ll a,ll pp){if(a>=0 && a<pp)return a;a%=pp;if(a<0)a+=pp;return a;}
ll powmod(ll a,ll b,ll pp){ll ans=1;for(;b;b>>=1,a=mo(a*a,pp))if(b&1)ans=mo(ans*a,pp);return ans;}
ll read(){
    ll ans=0;
    char last=' ',ch=getchar();
    while(ch<'0' || ch>'9')last=ch,ch=getchar();
    while(ch>='0' && ch<='9')ans=ans*10+ch-'0',ch=getchar();
    if(last=='-')ans=-ans;
    return ans;
}
//head

struct edge{
    int u,v,w;
}edg[n];
int d[n],n,m,s;
int main()
{
    n=read(),m=read(),s=read()
    rep(i,1,n) d[i]=0x7fffffff;
    for(int u ,v,w,i=1;i<=m;i++)
    {
        cin>>u>>v>>w,edg[i]=(edge){u,v,w};
    }
    d[s]=0;
    rep(i,1,n)
    {
        rep(j,1,m)
        {
            int u=edg[j].u,v=edg[j].v,w=edg[j].w;
            if(d[v]>d[u]+w) d[v]=d[u]+w,tag=true;
        }
        if(!tag)break;
    }
}

 

SPFA
Intuitive ideas
Bellman-Ford 算法中,如果 d(u) 在上一次全局更新中没有被更新,
那么这一次全局更新中不必松弛 u 的出边(就是不用它更新其他边)
因为这个操作在上一次中已经做过了
在 Bellman-Ford 的基础上改进
维护队列 Queue,记录哪些点的出边有必要松弛
取出 Queue 最前端的点 u
枚举 u 的所有出边 e : (u,v,w)
尝试松弛 e,若 d(v) > d(u) + w,更新 d(v),同时如果 v 不 在 Queue中,将 v 加入 Queue 中

算法何时终止?
记录每个点加入 Queue 的次数
u 被加入 Queue 一次,意味着 d(u) 被更新了一次
u 最多被更新 n − 1 次,否则肯定有负权环
时间复杂度理论上是O(mn),但肯定不劣于 Bellman-ford,实际远不到 O(nm)

Detail
更新 d(v) 后,如何判断 v 是不是已经在 Queue 里
用一个 Bool 数组记录就行, in/out Queue 的时候标记
怎么记录一个点进入 Queue 的次数
用一个计数数组记录就行, in/out Queue 的时候标记
优势: SPFA 在解决单源最短路问题中很高效
越稀疏越快(网格图会炸)

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 5;
const int inf = 1 << 29;

struct edge{
    int u, v, w;
};
vector<edge> edg[N];
int d[N], n, m, S;

queue<int> Queue;
bool inQueue[N];
int cntQueue[N];

void add(int u, int v, int w)
{
    edg[u].push_back((edge){u, v, w});
}
int main()
{
    cin >> n >> m >> S;
    for (int i = 1; i <= n; i++) d[i] = inf;
    for (int u, v, w, i = 1; i <= m; i++)
        cin >> u >> v >> w, add(u, v, w);
        
    d[S] = 0; inQueue[S] = true; Queue.push(S);
    while (!Queue.empty())
    {
        int u = Queue.front(); Queue.pop(); inQueue[u] = false;
        for (int e = 0; e < edg[u].size(); e++)
        {
            int v = edg[u][e].v, w = edg[u][e].w;
            if (d[v] > d[u] + w)
            {
                d[v] = d[u] + w;
                if (!inQueue[v])
                {
                    Queue.push(v); ++cntQueue[v]; inQueue[v] = true;
                    if (cntQueue[v] >= n) {cout << "Negative Ring" << endl; return 0;}
                }
            }
        }
    }
    for (int i = 1; i <= n; i++)
        cout << d[i] << endl;
}

 

本蒟蒻博客:传送门

标签:edg,int,Queue,edge,上午,visited,include,Day
来源: https://www.cnblogs.com/lcezych/p/10801912.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有