FZU ACM 2025寒假集训,专题7图论

一:做题思路

在这里插入图片描述

1.Stockbroker Grapevine

用弗洛伊德求出所有点之间的最短距离,再依次取从每个人开始传播给最后一个人(即用时最长的)时间,最后依次取这些最大值中的最小值并记录是从哪个人开始传播的即为答案,若不存在最小值则表明该图不连通。


#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
#define s 120
#define inf 0x3f3f3f3f
int  contact[s][s], n, t;
void floyd()//求所有点的最短路
{
	int i, j, k;
	for (k = 1; k <= t; k++)
	{
		for (i = 1; i <= t; i++){
			for (j = 1; j <= t; j++){
			   if (k == i || k == j || i == j) continue;
			   if (contact[i][k] + contact[k][j] < contact[i][j])
				contact[i][j] = contact[i][k] + contact[k][j];
			}
		}
	}
}
int main()
{
#ifdef OFFLINE
	freopen("t.txt", "r", stdin);
#endif
	int i, j, k, d, tt;
	while (~scanf("%d", &t) && t)
	{
		memset(contact, inf, sizeof(contact));
		for (i = 1; i <= t; ++i)
		{
			contact[i][i] = 0;
			scanf("%d", &n);
			while (n--){
				scanf("%d%d", &d, &tt);
				contact[i][d] = tt;
			}
		}
		floyd();
		int min = inf, max, flag;
		for (i = 1; i <= t; i++)
		{
			max = 0;
			for (j = 1; j <= t; j++){
				if (contact[i][j] > max)
				    max = contact[i][j];
	           //最后一个人获得 i 发出的消息的时间(最大)
			}
			if (max < min){//选其中传播时间最少的
				min = max;
				flag = i;
			}
		}
		if (min == inf)//非联通图
			printf("disjoint\n");
		else
			printf("%d %d\n", flag, min);
	}
	return 0;
}

2.树的直径

#include <iostream>
#include <vector>
#include <queue>
using namespace std;

vector<vector<int>> adj; // 邻接表
int n;

// 使用BFS找到距离节点u最远的节点和距离
pair<int, int> bfs(int u) {
    vector<bool> visited(n + 1, false);
    queue<pair<int, int>> q;
    q.push({u, 0});
    visited[u] = true;
    pair<int, int> farthest = {u, 0};
    while (!q.empty()) {
        pair<int, int> p = q.front();
        q.pop();
        int v = p.first;
        int dist = p.second;
        if (dist > farthest.second) {
            farthest = {v, dist};
        }
        for (int w : adj[v]) {
            if (!visited[w]) {
                visited[w] = true;
                q.push({w, dist + 1});
            }
        }
    }
    return farthest;
}

int main() {
    cin >> n;
    adj.resize(n + 1);
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    pair<int, int> farthest1 = bfs(1); // 从节点1开始找到最远的节点
    pair<int, int> farthest2 = bfs(farthest1.first); // 从最远的节点开始找到最远的节点
    cout << farthest2.second << endl; // 这两个最远的节点之间的距离就是树的直径
    return 0;
}

3.Invitation Cards

反向建图
在正向图中某个点到1的最短路就相当于在反向图中1到这个点的最短路。

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

const int INF = 1e9;
const int MAX = 1e6 + 2;
int N, P, Q;
bool inq[MAX];
int d[MAX];
int ans;

//vector<pair<int, int>> v[2][MAXP];

struct Edge
{
	int n1, n2, w;
};
vector<Edge> ve;
int first[MAX], nxt[MAX];

void init()
{
	memset(first, -1, sizeof first);
	memset(nxt, -1, sizeof nxt);
	ans = 0;
	ve.clear();
}

void spfa(int id)     // 这个id是指明spfa算法用边集中的n1还是n2作为当前点的后驱
{
	fill(d + 1, d + P + 1, INF);
	memset(inq, 0, sizeof inq);
	queue<int> q;
	d[1] = 0;
	inq[1] = true;
	q.push(1);

	for (; !q.empty();)
	{
		int t = q.front();
		q.pop();
		inq[t] = false;
		for (int i = first[t]; i != -1; i = nxt[i])
		{
			int u;
			if (!id) u = ve[i].n2;                  //
			else u = ve[i].n1;
			int w = ve[i].w;

			if (d[t] + w < d[u])
			{
				d[u] = d[t] + w;
				if (!inq[u])
				{
					q.push(u);
					inq[u] = true;
				}
			}
		}
	}
}

void reverse()
{
	memset(first, -1, sizeof first);
	memset(nxt, -1, sizeof nxt);
	for (int i = 0; i < ve.size(); i++)
	{
		nxt[i] = first[ve[i].n2];
		first[ve[i].n2] = i;
	}
}

int main()
{
	int a, b, c;
	scanf("%d", &N);
	for (; N--;)
	{
		scanf("%d%d", &P, &Q);
		init();
		for (; Q--;)
		{
			scanf("%d%d%d", &a, &b, &c);
			ve.push_back(Edge{ a, b, c });

			int index = ve.size() - 1;
			nxt[index] = first[a];                  // 链式前向星存图
			first[a] = index;
		}
		spfa(0);
		for (int i = 2; i <= P; i++) ans += d[i];
		reverse();
		spfa(1);
		for (int i = 2; i <= P; i++) ans += d[i];
		printf("%d\n", ans);
	}

    return 0;
}

4.战略游戏

dp[x][0/1]表示x这个节点不放/放士兵。如果当前节点放置士兵,只要将dp[x][1]加上它的子节点选不选的最小值就行了(因为树形dp自下而上,上面的节点不需要考虑)

dp[x][1]+=min(dp[son][0],dp[son][1])

#include <bits/stdc++.h>
#define int long long
using namespace std;
int n,u,v,dp[10001][2];
vector<int> vec[100001];
void dfs(int fa,int x)
{
  dp[x][1] = 1;
  for(int i = 0;i < vec[x].size();i++)
    if(vec[x][i] != fa)
    {
      dfs(x,vec[x][i]);
	  dp[x][0] += dp[vec[x][i]][1];
	  dp[x][1] += min(dp[vec[x][i]][0],dp[vec[x][i]][1]);
    }
}
signed main()
{
  cin>>n;
  for(int i = 1;i <= n;i++)
  {
    int p;
    cin>>p>>u;
    for(int j = 1;j <= u;j++)
    {
      cin>>v;
      vec[p].push_back(v);
      vec[v].push_back(p);
    }
  }
  dfs(-1,0);
  cout<<min(dp[0][1],dp[0][0]);
  return 0;
}

5.飞行路线

分层图最短路问题
考虑把图分成 k 层(就是每个点复制 k 次),第 i 层表示当前已经使用了 i 次免费机会。
将上下两层之间连上权值为0的边就可以了
我们将图分成0~k层这里就是将图分成k层,
每次从第i-1层到第i层相当于是走了一条免费的飞行路线。
然后如果从第i层回到第i-1层就是一个“后悔”的过程。
因此建图方法就是每层之间正常连边,层与层之间上到下边权为0,
下到上正常边权。这样直接跑Dijkstra就好了。

#include <bits/stdc++.h>

#define x first
#define y second
#define PII pair<int,int>

using namespace std;

const int M = 100010;

int f[M][15];
bool st[M];
int n,m,p,s,t;

int e[M],w[M],ne[M],h[M],idx ;

priority_queue<PII,vector<PII>,greater<PII> > q;

void add(int a ,int b ,int c)
{
    e[idx] = b, ne[idx] = h[a] , w[idx] = c , h[a] = idx ++;
}

void Djikstar()
{
    for(int i = 0 ; i <= p ; i ++) f[s][i] = 0;
    
    for(int k = 0 ; k <= p; k ++)
    {
          memset(st,0,sizeof st);
          q.push({0,s});
          
          while(q.size())
          {
              PII t = q.top(); q.pop();
              
              if(st[t.y]) continue;
              st[t.y] = true;
              
              for(int i = h[t.y] ; i != -1 ; i = ne[i])
              {
                    
                  bool is = false;
                  int j = e[i];
                  
                  if(k && f[j][k] > f[t.y][k -1])
                  {
                      f[j][k] = f[t.y][k - 1] ;
                      is = true;
                  }
                  if(f[j][k] > f[t.y][k] + w[i]) f[j][k] = f[t.y][k] + w[i] , is = true;
                  
                  if(is) q.push({f[j][k] , j});
              }
          }
    }
}

int main()
{
    memset(h,-1,sizeof h);
    memset(f,0x3f,M * 15);
    cin >> n >> m >> p >> s >> t;
    
    for(int i = 1 ; i <= m; i ++)
    {
        int x,y,z;
        cin >> x >> y >> z;
        add(x,y,z);
        add(y,x,z);
    }  
    Djikstar();
    
    cout << f[t][p] << endl;
    return 0;
}

6.二叉苹果树

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110, M = 2 * N;

//f[i][j]表示以i为根的子树保留j根树枝,留住了多少苹果 

int n, m;
int h[N], e[M], ne[M], w[M], idx;
int f[N][N];

void add(int a, int b, int c)
{
	e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

void dfs(int u, int father)
{
	for (int i = h[u]; ~i; i = ne[i])
	{
		if (e[i] == father) continue;
		dfs(e[i], u);
		
		for (int j = m; j >= 0; j--)
		{
			for (int k = 0; k < j; k++)
			{
				f[u][j] = max(f[u][j], f[u][j - 1 - k] + f[e[i]][k] + w[i]);
			}
		} 
	}
} 

int main()
{
    cin >> n >> m;
	memset(h, -1, sizeof h);
	for (int i = 0; i < n - 1; i++)
	{
		int a, b, c;
		scanf("%d%d%d", &a, &b, &c);
		add(a, b, c);
		add(b, a, c);
	}
	
	
	dfs(1, -1);
	
	cout << f[1][m] << endl;
	
	return 0;
} 

二:知识点总结

基本定义

在这里插入图片描述

邻接矩阵

在这里插入图片描述
只适用于没有重边(或重边可以忽略)的情况。
在这里插入图片描述

int G[N][N];
 void add(int x,int y) {
 G[x][y] = 1;
 }
 void init() {
 //初始化整张图
memset(G,0x3f,sizeof G);
 for (int i = 1;i <= n;++i) G[i][i] = 0;
 }
 //遍历全图
for (int i = 1;i <= n;++i) {
 for (int j = 1;j <= n;++j) {
 if (i == j) continue;
 }
 }

邻接表

用 vector 实现(邻接表):

#include <iostream>
#include <vector>

using namespace std;

class Graph {
private:
    int V; // 顶点数
    vector<vector<int>> adj; // 邻接表,存储每个顶点的边

public:
    // 构造函数
    Graph(int vertices) : V(vertices), adj(V) {}

    // 添加无向边
    void addEdge(int src, int dest) {
        adj[src].push_back(dest); // 无向图:添加双向边
        adj[dest].push_back(src);
    }

    // 添加有向边
    void addDirectedEdge(int src, int dest) {
        adj[src].push_back(dest); // 有向图:只添加单向边
    }

    // 打印邻接表
    void printGraph() {
        for (int i = 0; i < V; ++i) {
            cout << "Vertex " << i << ": ";
            for (int j : adj[i]) {
                cout << j << " ";
            }
            cout << endl;
        }
    }
};

int main() {
    int V = 5; // 顶点数
    Graph graph(V);

    // 添加边
    graph.addDirectedEdge(0, 1);
    graph.addDirectedEdge(0, 4);
    graph.addDirectedEdge(1, 2);
    graph.addDirectedEdge(1, 3);
    graph.addDirectedEdge(1, 4);
    graph.addDirectedEdge(2, 3);
    graph.addDirectedEdge(3, 4);

    // 打印邻接表
    cout << "Adjacency List of the Graph:" << endl;
    graph.printGraph();

    return 0;
}

三个最短路算法

最短路

在这里插入图片描述

void Floyd() {
 for (int k = 1;k <= n;++k) {
 for (int i = 1;i <= n;++i) {
 for (int j = 1;j <= n;++j) {
 f[i][j] = min(f[i][j],f[i][k] + f[k][j]);
 }
 }
 }
 }

Dijkstra

只能处理正权边。通过优先队列优化.

int n,m,s,dis[N];
 bool vis[N];
的时间复杂度:也就是
struct node {
 int pos,dis;
 friend bool operator < (const node &a,const node &b) {
 return a.dis > b.dis;
 }
 };
 priority_queue <node> q;
 void Dijkstra(int s) {
 memset(dis,0x3f,sizeof dis);
 memset(vis,0,sizeof vis);
 q.push((node){s,dis[s] = 0});
 while (!q.empty()) {
node p = q.top();q.pop();
 int x = p.pos;
 if (vis[x]) continue;
 vis[x] = 1;
 for (auto e : G[x]) {
 int y = e.first,w = e.second;
 if (dis[y] > dis[x] + w) {
 dis[y] = dis[x] + w;
 q.push((node){y,dis[y]});
 }
 }
 }
 }

Bellman Ford && SPFA

在这里插入图片描述

struct Edge {
 int u, v, w;
 };
 vector<Edge> edge;
 int dis[MAXN], u, v, w;
 constexpr int INF = 0x3f3f3f3f;
 bool bellmanford(int n, int s) {
 memset(dis, 0x3f, (n + 1) * sizeof(int));
 dis[s] = 0;
bool flag = false;  // 判断一轮循环过程中是否发生松弛操作
for (int i = 1; i <= n; i++) {
 flag = false;
 for (int j = 0; j < edge.size(); j++) {
 u = edge[j].u, v = edge[j].v, w = edge[j].w;
 if (dis[u] == INF) continue;
 // 无穷大与常数加减仍然为无穷大
// 因此最短路长度为 INF 的点引出的边不可能发生松弛操作
if (dis[v] > dis[u] + w) {
 dis[v] = dis[u] + w;
 flag = true;
 }
 }
 // 没有可以松弛的边时就停止算法
if (!flag) {
 break;
 }
 }
 // 第 n 轮循环仍然可以松弛时说明 s 点可以抵达一个负环
return flag;
 }

在这里插入图片描述
时长较长会被卡

struct edge {
 int v, w;
 };
 vector<edge> e[MAXN];
 int dis[MAXN], cnt[MAXN], vis[MAXN];
 queue<int> q;
 bool spfa(int n, int s) {
 memset(dis, 0x3f, (n + 1) * sizeof(int));
 dis[s] = 0, vis[s] = 1;
 q.push(s);
 while (!q.empty()) {
int u = q.front();
 q.pop(), vis[u] = 0;
 for (auto ed : e[u]) {
 int v = ed.v, w = ed.w;
 if (dis[v] > dis[u] + w) {
 dis[v] = dis[u] + w;
 cnt[v] = cnt[u] + 1;  // 记录最短路经过的边数
if (cnt[v] >= n) return false;
 // 在不经过负环的情况下,最短路至多经过 n - 1 条边
// 因此如果经过了多于 n 条边,一定说明经过了负环
if (!vis[v]) q.push(v), vis[v] = 1;
 }
 }
 }
 return true;
 }

定义

父亲(parent node):对于除根以外的每个结点,定义为从该结点到根路径上的第
二个结点。根结点没有父结点。
祖先(ancestor):一个结点到根结点的路径上,除了它本身外的结点。根结点的祖
先集合为空。
子结点(child node):如果 是 的父亲,那么 是 的子结点。子结点的顺序
一般不加以区分,二叉树是一个例外。
结点的深度(depth):到根结点的路径上的边数。
树的高度(height):所有结点的深度的最大值。
兄弟(sibling):同一个父亲的多个子结点互为兄弟。
直径:树上任意两节点之间最长的简单路径即为树的「直径」。
链(chain/path graph):满足与任一结点相连的边不超过
条的树称为链。
菊花/星星(star):满足存在
使得所有除
以外结点均与
相连的树称为菊花。
有根二叉树(rooted binary tree):每个结点最多只有两个儿子(子结点)的有根树称为
二叉树。常常对两个子结点的顺序加以区分,分别称之为左子结点和右子结点。
大多数情况下,二叉树 一词均指有根二叉树。
完整二叉树(full/proper binary tree):每个结点的子结点数量均为 0 或者 2 的二叉
树。换言之,每个结点或者是树叶,或者左右子树均非空。
完全二叉树(complete binary tree):只有最下面两层结点的度数可以小于 2,且最下
面一层的结点都集中在该层最左边的连续位置上。
完美二叉树(perfect binary tree):所有叶结点的深度均相同,且所有非叶节点的子节
点数量均为 2 的二叉树称为完美二叉树。

存储以及遍历

左孩子右兄弟表示法
int v = child[u];  // 从第一个子结点开始
while (v != EMPTY_NODE) {
 // ...
 // 处理子结点 v
 // ...
 }
DFS 序
实际实现很简单,我们只需要在 DFS 开头加上一句就可以:
void dfs(int x,int fa) {
 dfn[x] = ++cnt;
 for (auto y : G[x]) {
 if (y == fa) continue;
 dfs(y,x);
 }
 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值