【PAT】PAT总结《图论、动态规划》

图论判别题

这类题一般是给出一个图和一组查询,让你判断这组查询(一般是点集)是否是某种图或者某种路径或者某种点集。
首先要考虑针对顶点判别还是针对边判别
其次是选择邻接向量还是布尔矩阵或者是带权矩阵,我们要根据题目给的点集范围以及判别过程中需要获取点的相关边还是直接获取点和点之间的连通性信息,来选择数据结构。如果需要获取点的相关边,那么使用邻接向量比较方便,如果需要点和点之间的连通信息,使用布尔向量比较方便。如果既需要判断连通性,又需要快速(给出两个点)查询边权,应该使用带权矩阵。如果不需要直接查询两个点之间的边,而图又比较稀疏,那么应该使用邻接向量。
对于判别,可以正向考虑也可以逆向考虑。正向考虑就是满足什么条件的情况下,它是某种概念(比如哈密顿圈)等等,根据这些条件去判别。逆向考虑就是它不满足什么条件时,会降低成什么概念(比如不是圈,那一定是非TS圈,不是简单圈,一定是TS圈,否则是TS简单圈)。从这两个角度去考虑。逆向考虑的时候,如果逻辑有疏漏,可能会导致错误。如果找不出bug,可以考虑换个考虑(正向换逆向,逆向换正向)。
常常要考虑的问题:

  • 图/路径是否存在环。
  • 路径是否走了重复的顶点。
  • 图是否是连通图。以及题给的概念和连通图之间的关系。(比如非连通图不可能存在欧拉回路和欧拉路径)
  • 当图是桩(即只有一个顶点)的时候,它属于哪一类。
  • 当路径/点集满足什么条件时,可以将它的概念限定在什么范围。(比如,当一个路径的顶点数少于图中的顶点数,那么它不可能是哈密顿圈)
  • 查询是否一定合法(比如给出的查询可能包含无效路径/无效顶点)
  • 是否会出现平行边(目前还没有这种题,但是是可能的)
  • 是否会出现自环(目前也没有出现过,但是是可能的)

对于圈(当然,首先要判断这条路径首位顶点相同,是一个圈),我们常常排除路径的第一个点或者最后一个点然后进行计数,这样计数的时候,所有的顶点都只出现一次,也就意味着,首尾顶点出现了两次。
二重遍历的时候,要考虑是有向图还是无向图,如果是无向图的话是一个组合问题,对于u->v和v->u是等价的,所以我们可以总假设v>u来判别u->v即可。如果是有向图,那么是一个排列问题,显然u->v和v->u不等价。
下面我写的代码有一点点小瑕疵,如果只是需要判断是否经过一个点,使用布尔数组即可,不需要使用整型数组计数。
判断连通性可以使用DFS也可以使用并查集。DFS:从任意一个顶点开始执行一轮DFS,如果还有未被访问的顶点说明不连通。并查集:遍历边将顶点合并,最终连通分量为1说明连通,否则不连通。

欧拉回路

如果一个无向图是连通的,且最多只有两个奇点(或者0个),则一定存在欧拉道路。如果有两个奇点,则必须从其中一个奇点出发,另一个奇点终止;如果奇点不存在,则可以从任意点出发,最终一定会回到该点(称为欧拉回路,欧拉回路是特殊的欧拉道路)。

A1122 Hamiltonian Cycle

A1122 Hamiltonian Cycle
哈密顿圈的判别

A1126 Eulerian Path

A1126 Eulerian Path
欧拉图的判别,记得判断连通性

A1142 Maximal Clique

A1142 Maximal Clique

A1150 Travelling Salesman Problem

A1150 Travelling Salesman Problem
中国邮递员问题

A1146 Topological Order

A1146 Topological Order
拓扑序列的判别

A1134 Vertex Cover

A1134 Vertex Cover
这道题要求的是一条边至少有一个顶点被覆盖。因为一个顶点可能有多条边与之相连,从顶点的角度来考虑比较复杂,那么显然应该对边考虑。

A1131 Subway Map

这道题应该最好使用BFS寻最短路,群里有人说什么Dijkstra,其实是Dijkstra写多了盲目使用,首先得明白这是一个无向无权图,所以使用BFS就可以了,当然写出来和Dijkstra确实很像。无奈有一个点没有过,所以就不贴代码和思路了。柳婼使用了DFS,在这道题里没问题,但是数据刁钻一点可能就爆了。传送门

单源最短路径

基本上就是使用Dijkstra算法,这类题都快写吐了,上手Dijkstra。下面给出常用的几个问题的规律:

  • 关于计数路径数量,将起点设置为road[source] = 1,对于放松的每个顶点v,road[v] = road[u](因为此时必须从u走到v,所以从起点到u的路径数就是从起点到v的路径数),对于找到经过u的等权路径到达v,road[v] += road[u](此时相当于走到v多了从u到v这一分支,那么多出来的路径数就是从起点到u的路径数)。
  • 关于第二个标量的处理,在第一个标量相等的情况下判断第二个标量是否更优,更优则更新路线(但是不需要放松)。
  • 关于点权的处理,题目常常会要求最短路径的同时点权和最大,这时候可以贪心处理,在找到等权路径的时候,判断w[u] + w[v]是否大于w[v],是则更新它。也有可能是除了路径的其他边权,那就是w[u] + e.w2(w2代表第二个标量)是否大于w[v]。总之只要这个量是可以贪心的,我们就可以在等权路径上更新它。目前只有一道题例外,就是A1018 Public Bike Management。
  • 如果需要遍历多条路径,那么可以将edgeTo改成向量数组,放松的时候清空向量,加入新的u,找到等权路径的时候,加入新的u。
  • 要注意优先队列的定义,这个我被坑了好几次。

模板

	// 初始化
	fill(distTo, distTo + N, INT_MAX);
    fill(timeTo, timeTo + N, INT_MAX / 2);
    fill(edgeTo, edgeTo + N, -1);
    fill(marked, marked + N, false);
    
    priority_queue<Node> pq;
    // 不要处理起点
    distTo[source] = 0;
    timeTo[source] = 0;
    pq.push(Node(source, 0));
    int u, v;
    while (!pq.empty()) {
        u = pq.top().to;
        pq.pop();
        if(marked[u]) continue;
        if(u == destination) break;// 找到终点,退出
        marked[u] = true;// 记得标记为true,否则会无限循环
        for(Edge& e : G[u]){
            v = e.to;
            if(marked[v]) continue;// 过滤,否则会无限循环
            if(distTo[u] + e.length < distTo[v]){
                distTo[v] = distTo[u] + e.length;
                edgeTo[v] = u;
                timeTo[v] = timeTo[u] + e.time;
                pq.push(Node(v, distTo[v]));
            }else if(distTo[u] + e.length == distTo[v] && timeTo[u] + e.time < timeTo[v]){
                timeTo[v] = timeTo[u] + e.time;
                edgeTo[v] = u;
            }
        }
    }
    
    // 获取路径(包括起点和终点)
    for(int x = destination; x != -1; x = edgeTo[x]){
        path.push_back(x);
    }
struct Node{
    int to;
    int cost;
    Node(int to, int cost) : to(to), cost(cost){};
};
bool operator < (Node a, Node b){// 权大的优先级小,在堆的下层
    return a.cost > b.cost;
}

DFS临时路径

一般有两种做法,一种是类似edgeTo这种,在dfs的时候记录每个顶点的上一个顶点。但是参数需要传两个顶点不是很方便,在最外层可以调用dfs(source, -1),用temp[source] = -1代表source是路径的起点。

void dfs(int u, int v){// u为当前要遍历的顶点,v为上一个顶点
	temp[u] = v;
	//...
	for(int w : adj(u){
		dfs(w, u);
	}
	//...
}

还有一种是

void dfs(int u){
	if(出口条件){
		path.pop_back(u);// 这句一定不能少,在出口处要回溯。
		return;
	}
	path.push_back(u);// 加入顶点
	// 逻辑和dfs部分
	path.pop_back(u);// 回溯
}

这种其实就是一种回溯写法。

多源最短路

这类题还没有考过,但是甲级的考纲里写的是“最短路径”而不是“单源最短路径”。

关键路径

做了一个水题,不过感觉如果考不会考太难。

HDU4109 Instrction Arrangement

【关键路径】HDU4109 Instrction Arrangement

floyd模板

// 初始化
        for(int i = 0; i < n; i++){
            for(int j = i + 1; j < n; j++){
                d[i][j] = d[j][i] = oo;
            }
            d[i][i] = 0;
        }
        
        // floyd,只需要更新到n即可
        for(int k = 0; k < n; k++){
            for(int i = 0; i < n; i++){
                for(int j = 0; j < n; j++){
                    d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
                }
            }
        }

【floyd算法】UVA821 Page Hopping

【floyd算法】UVA821 Page Hopping

PAT题目

A1003 Emergency 救援最短路径和最大救援部队
A1072 Gas Station
A1087 All Roads Lead to Rome
A1030 Travel Plan
A1018 Public Bike Management
A1111 Online Map

动态规划

由于PAT甲级已经明确不考动态规划,所以这一类题暂时放一放。而且PAT考察的动态规划类型也不全面。
A1057 Stack|树状数组
1007 Maximum Subsequence Sum| 动态规划
A1045 Favorite Color Stripe|最长不下降子序列
A1040 Longest Symmetric String|最长回文子串
A1068 Find More Coins|0-1背包

值得二刷的题

  • A1134 Vertex Cover一般想不到对边考虑
  • A1018 Public Bike Management不能贪心的一道Dijkstra题
  • A1111 Online Map除了A1018最难的一道Dijkstra题。

你可能感兴趣的:(PAT)