目录
A1150 Travelling Salesman Problem
图的遍历
DFS
A1013 Battle Over Cities
题意:构建一个图,当去除某个节点时,需要构建几条边使得图再次连通。
解法:三刷了,好久没做图又有些忘了。
总结一下思路,首先要构造一张图。用邻接矩阵存储,1表示两个节点连通。
然后DFS遍历这个图,遍历图时由于需要避免重复遍历,因此需要一个vis数组标记每个节点是否被访问过。DFS遍历的方法是,遍历所有节点,如果该节点未被访问过,且与当前节点有边相连,递归遍历这个节点。
由于涉及删除某个节点,这里使用判断当前节点是否是删除的节点,如果是则直接return的方法。
然后DFSTrave()是用来遍历整张可能不连通的图,返回连通块个数。遍历方法是,直接遍历所有节点,如果u不是删除的节点cur(u != v)且不曾访问过(vis[u] == false),则对u进行DFS遍历,DFS遍历会标记一整个连通块,有多少个连通块则会调用多少次DFS遍历,因此得到连通块个数。
连通块个数 - 1便是要添加的边数。
#include<cstdio>
#include<cstring>
const int maxn = 1010;
int G[maxn][maxn] = {0};
bool vis[maxn] = {false};
int n, cur;
void DFS(int u, int depth) {
if(u == cur) return;
vis[u] = true;
for(int v = 1; v <= n; v ++) {
if(vis[v] == false && G[u][v] != 0 && G[v][u] != 0)
DFS(v, depth + 1);
}
}
int DFSTrave(int v) {
int cnt = 0;
for(int u = 1; u <= n; u ++) {
if(v != u && vis[u] == false) {
DFS(u, 1);
cnt ++;
}
}
return cnt;
}
int main() {
int m, k, c1, c2;
scanf("%d %d %d", &n, &m, &k);
for(int i = 0; i < m; i ++) {
scanf("%d %d", &c1, &c2);
G[c1][c2] = G[c2][c1] = 1;
}
for(int i = 0; i < k; i ++) {
scanf("%d", &cur);
memset(vis, false, sizeof(vis));
printf("%d\n", DFSTrave(cur) - 1);
}
return 0;
}
A1021 Deepest Root
题意:给出N个节点和N - 1条边,问他们能否形成一棵N个节点的树,如果能,则从中选出节点作为树根,使得整棵树的高度最大。输出所有满足要求的树根。
分析:看数据量大小,最大为10000,而邻接矩阵适合顶点数量不超过1000的情况,因此本题选择邻接表存储图。
首先判断图是否连通:使用并查集计算连通块个数。
然后任意一个节点进行DFS遍历,得到最深的根节点的集合A,在从集合A中选一个节点进行DFS遍历得到最深的根的集合B,A和B的并集便是结果。
坑点:注意并查集的初始化,下标为1到n(节点编号)。
#include<cstdio>
#include<vector>
#include<set>
using namespace std;
const int maxn = 10010;
vector<int> Adj[maxn];
//bool vis[maxn] = {false};
int father[maxn];
bool isRoot[maxn] = {false};
int findFather(int x) {
int a = x;
while(x != father[x])
x = father[x];
while(a != father[a]) {
int z = a;
a = father[a];
father[z] = x;
}
return x;
}
void Union(int a, int b) {
int faA = findFather(a);
int faB = findFather(b);
if(faA != faB)
father[faA] = faB;
}
void init(int n) {
for(int i = 1; i <= n; i ++) {
father[i] = i;
isRoot[i] = false;
}
}
/*计算连通块个数*/
int calBlock(int n) {
int block = 0;
for(int i = 1; i <= n; i ++) {
isRoot[findFather(i)] = true;
}
for(int i = 1; i <= n; i ++) {
if(isRoot[i])
block ++;
}
return block;
}
int maxH = 0;
set<int> temp, ans;
void DFS(int u, int depth, int pre) {
if(depth > maxH) {
temp.clear();
temp.insert(u);
maxH = depth;
}
else if(depth == maxH) {
temp.insert(u);
}
for(int i = 0; i < Adj[u].size(); i ++) {
if(Adj[u][i] == pre) continue; //跳过回去的边
DFS(Adj[u][i], depth + 1, u);
}
}
int main() {
int n;
scanf("%d", &n);
init(n);
for(int i = 0; i < n - 1; i ++) {
int a, b;
scanf("%d %d", &a, &b);
Adj[a].push_back(b);
Adj[b].push_back(a);
Union(a, b);
}
int block = calBlock(n);
if(block > 1) printf("Error: %d components\n", block);
else {
DFS(1, 1, -1);
ans = temp; //集合A(deepest root)
DFS(*ans.begin(), 1, -1); //从集合A中找一个点DFS
ans.insert(temp.begin(), temp.end()); //合并集合A、B
for(auto it = ans.begin(); it != ans.end(); it ++) {
printf("%d\n", *(it));
}
}
return 0;
}
A1034 Head of a Gang
题意:无向图,通话关系为边,边权为通话时长,给定一个阈值K,当一个连通块的总边权超过K且人数大于2,则为犯罪团伙Gang,点权最大的为head。
解法:图的遍历。首先将姓名和编号使用map存储。图中的节点的点权为与之相关的边权的和。
DFS函数遍历一个连通块,传入当前节点,同时传入记录变量(引用&)头目head编号,成员数numMember,总边权totalValue。记住图的遍历需要有一个vis标记节点是否访问过。
DFSTrave负责遍历整张图,计算每个连通块的信息。
#include<iostream>
#include<map>
#include<string>
using namespace std;
const int maxn = 2010;
map<int, string> id2name;
map<string, int> name2id;
map<string, int> Gang;
int G[maxn][maxn] = {0}, weight[maxn] = {0}; //点权
int n, k, numPerson = 0;
bool vis[maxn] = {false};
void DFS(int now, int& head, int& numMember, int& totalValue) {
numMember ++;
vis[now] = true;
if(weight[now] > weight[head]) {
head = now;
}
for(int i = 0; i < numPerson; i ++) {
if(G[now][i] > 0) {
totalValue += G[now][i];
G[now][i] = G[i][now]; //删除这条边防止回头
if(vis[i] == false)
DFS(i, head, numMember, totalValue);
}
}
}
/*遍历整个图,获得每个连通块的信息*/
void DFSTrave() {
for(int i = 0; i < numPerson; i ++) {
if(vis[i] == false) {
int head = i, numMember = 0, totalValue = 0;
DFS(i, head, numMember, totalValue);
if(numMember > 2 && totalValue / 2 > k) {
Gang[id2name[head]] = numMember;
}
}
}
}
int change(string str) {
if(name2id.find(str) != name2id.end()) {
return name2id[str];
}
else {
name2id[str] = numPerson;
id2name[numPerson] = str;
return numPerson ++;
}
}
int main() {
int w;
string a, b;
cin >> n >> k;
for(int i = 0; i < n; i ++) {
cin >> a >> b >> w;
int id1 = change(a);
int id2 = change(b);
weight[id1] += w;
weight[id2] += w;
G[id1][id2] += w;
G[id2][id1] += w;
}
DFSTrave();
cout << Gang.size() << endl;
for(auto it : Gang) {
cout << it.first << " " << it.second << endl;
}
return 0;
}
A1131 Subway Map
题意:求两站之间的最短路径,如果站点数相同,选择换乘站最少的的那条。
解法:难题。一开始,傻傻的再用Dijkstra + DFS,结果发现有三个样例过不了。一查发现网上都是用DFS解的。思考一下,要求某个点到另一个点的深度最小的路径,DFS就行了。看了柳神的代码。总结一下思路。
关键思路,线路号的确定:一个站点可能属于两跳不同的线路,但是两个相邻的站点(这里称之为短线)之间的线路一定属于某一条唯一的线路。 因此记录两个站点之间的线路编号,在读入时就记录。一个很好的编码方式是用map<int, int>来保存某一短线的编号对应的线路号,即(站点1,站点2)-> 线路号。而站点1到站点2可以用它们的编号编码成一个整数,即num[1] * 10000 + num[2]。
此时,统计换乘站,就可以遍历路径,如果相连的两条短线编号不相等,则是换乘站,计数加1、
DFS的逻辑:传入参数index和cnt(分别表示顶点编号和深度),递归边界是index == ed。在递归边界记录更小的站点数和更小的换乘数保存最优路径。然后递归遍历邻接顶点,如果顶点未访问过(vis[v[index][i]] == false)则将其置为true,将顶点加入tempPath,然后递归进入DFS,退出递归时回溯复原。注意DFS遍历图时,需要有图的邻接表数据表示和记录顶点访问情况的vis数组。
这里的DFS没有将开始顶点加入路径,所以在函数外单独处理(这感觉不太好)。
最后遍历答案路径按要求输出。
#include<iostream>
#include<vector>
#include<unordered_map>
using namespace std;
const int maxn = 10000;
const int INF = 1000000000;
vector<vector<int>> v(maxn); //邻接表
bool vis[maxn] = {false};
int minCnt, minTransfer, st, ed;
unordered_map<int, int> line;//(站点A->站点B)的编号(A * 10000 + B)->线路号
vector<int> path, tempPath;
int transferCount(vector<int> a) {
int cnt = -1, preLine = 0;
for(int i = 1; i < a.size(); i ++) {
if(line[a[i - 1] * 10000 + a[i]] != preLine) cnt ++;
preLine = line[a[i - 1] * 10000 + a[i]] ;
}
return cnt;
}
void DFS(int index, int cnt) {
if(index == ed) { //递归边界
if(cnt < minCnt || (cnt == minCnt && transferCount(tempPath) < minTransfer)) {
minCnt = cnt;
minTransfer = transferCount(tempPath);
path = tempPath;
}
return;
}
for(int i = 0; i < v[index].size(); i ++) {
if(vis[v[index][i]] == false) {
vis[v[index][i]] = true;
tempPath.push_back(v[index][i]);
DFS(v[index][i], cnt + 1);
vis[v[index][i]] = false;
tempPath.pop_back();
}
}
}
int main() {
int n, m, k, pre, temp;
scanf("%d", &n);
for(int i = 1; i <= n; i ++) {
scanf("%d %d", &m, &pre);
for(int j = 1; j < m; j ++) {
scanf("%d", &temp);
v[pre].push_back(temp);
v[temp].push_back(pre);
line[pre * 10000 + temp] = line[temp * 10000 + pre] = i;
pre = temp;
}
}
scanf("%d", &k);
for(int i = 0; i < k; i ++) {
scanf("%d %d", &st, &ed);
minCnt = INF, minTransfer = INF;
tempPath.clear();
tempPath.push_back(st);
vis[st] = true;
DFS(st, 0);
vis[st] = false;
printf("%d\n", minCnt);
int preLine = 0, preTransfer = st;
for(int j = 1; j < path.size(); j ++) {
if(line[path[j - 1] * 10000 + path[j]] != preLine) {
if(preLine != 0)
printf("Take Line#%d from %04d to %04d.\n", preLine, preTransfer, path[j - 1]);
preLine = line[path[j - 1] * 10000 + path[j]];
preTransfer = path[j - 1];
}
}
printf("Take Line#%d from %04d to %04d.\n", preLine, preTransfer, ed);
}
return 0;
}
BFS
A1076 Forwards on Weibo
题意:求L跳邻居节点总数量。
分析:使用BFS遍历图。由于需要记录层号,因此需要将节点编号和层号建立成结构体。
注意边的连接方向。题目给的的某个用户i关注的用户列表,即我关注的n个节点,因为我关注的人不能转发我发的微博,而只有关注我的人才能转发我的微博。由于要求L跳可以转发我的微博的邻居的数量,因此应该将边的意义定义为被关注,存储时注意边的方向,否则会遍历失败。Adj[idFollow].push_back(user);
记住BFS遍历图需要一个inq来记录节点是否进入过队列。
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
const int maxn = 1010;
struct node {
int id;
int layer;
};
vector<node> Adj[maxn]; //邻接表
bool inq[maxn] = {false};
int BFS(int s, int L) {
int numForward = 0;
queue<node> q;
node start;
start.id = s;
start.layer = 0;
q.push(start);
inq[start.id] = true;
while(!q.empty()) {
node now = q.front();
q.pop();
int u = now.id;
for(int i = 0; i < Adj[u].size(); i ++) {
node next = Adj[u][i];
next.layer = now.layer + 1;
if(inq[next.id] == false && next.layer <= L) {
q.push(next);
inq[next.id] = true;
numForward ++;
}
}
}
return numForward;
}
int main() {
node user;
int n, L, numFollow, idFollow;
scanf("%d %d", &n, &L);
for(int i = 1; i <= n; i ++) {
user.id = i;
scanf("%d", &numFollow);
for(int j = 0; j < numFollow; j ++) {
scanf("%d", &idFollow);
Adj[idFollow].push_back(user);
}
}
int numQuery, s;
scanf("%d", &numQuery);
for(int i = 0; i < numQuery; i ++) {
memset(inq, false, sizeof(inq));
scanf("%d", &s);
int numForward = BFS(s, L);
printf("%d\n", numForward);
}
return 0;
}
最短路径
解决最短路径的算法有:Dijkstra算法,Bellman-Ford算法,SPFA算法、Floyd算法。
单源最短路径:Dijkstra算法
带有负边权的最短路径问题:Bellman-Ford算法
Bellman-Ford算法的优化版本:SPFA算法
全源最短路径:Floyd算法
Dijkstra算法的模板(邻接矩阵版)
适用于v不超过1000的情况。复杂度O(V^2)。
const int MAXV = 1000;
const int INF = 1000000000; //or 0x3fffffff;
int n, G[MAXV][MAXV];
int d[MAXV];
int pre[MAXV];
bool vis[MAXV] = {false};
void Dijkstra(int s) {
fill(d, d + MAXV, INF); //fill函数将整个d数组赋值为INF(慎用memset)
d[s] = 0;
for(int i = 0; i < n; i ++) { //循环n次
int u = -1, MIN = INF;
for(int j = 0; j < n; j ++) {//找到未访问的顶点中最小的d[u]
if(vis[j] == false && d[j] < MIN) {
u = j;
MIN = d[j];
}
}
if(u == -1) return; //找不到说明剩下的顶点和起点s不连通
vis[u] = true; //标记u为已访问
for(int v = 0; v < n; v ++) {
//如果v未访问且u能到达v且以u为中介点可使d[v]更优
if(vis[v] == false && G[u][v] != INF && d[u] + G[u][v] < d[v]) {
d[v] = d[u] + G[u][v]; //优化d[v]
pre[v] = u; //记录v的前驱顶点是u
}
}
}
}
Dijkstra算法的模板(邻接表版)
O(V2 + E)。可以使用堆优化寻找最小d[u],最简便的写法是利用STL的priority_queue, 使得复杂度降到O(VlogV + E).
struct Node {
int v, dis; //v为bian的目标顶点,dis为边权。
};
vector<Node> Adj[MAXV];
int n;
int d[MAXV] = {false};
void Dijkstra(int s) {
fill(d, d + MAXV, INF);
d[s] = 0;
for(int i = 0; i < n; i ++) { //循环n次
int u = -1, MIN = INF; //找到未访问的顶点中d[]最小的u
for(int j = 0; j < n; j ++) {
if(vis[j] == false && d[j] < MIN) {
u = j;
MIN = d[j];
}
}
if(u == -1) return;//找不到
vis[u] = true;
//只有下面这个for循环与邻接矩阵写法不同
for(int j = 0; j < Adj[u].size(); j ++) {
int v = Adj[u][j].v;//通过邻接表直接获得能到达的顶点v
if(vis[v] == false && d[u] + Adj[u][j].dis < d[v]) {
d[v] = d[u] + Adj[u][j].dis;//优化d[v]
pre[v] = u;//记录v的前驱顶点是u
}
}
}
}
当最短路径不止一条时,通常先用Dijkstra算法找到并保存相同的最短路径,然后DFS遍历这些路径,按题目要求输出。
A1003 Emergency
题意:求最短路径,当不止一条时求条数,以及最短点权之和。
分析:设置数组d记录最短路径,数组w记录最短点权之和,数组num记录最短路径条数。除了图G之外,一个weight数组记录输入的点权。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 510;
const int INF = 1000000000;
int G[maxn][maxn], weight[maxn];
int n, m, st, ed;
int d[maxn], w[maxn], num[maxn]; //d记录最短距离,w记录最短点权之和,num记录最短路径条数
bool vis[maxn] = {false};
void Dijkstra(int s) {
fill(d, d + maxn, INF);
memset(w, 0, sizeof(w));
memset(num, 0, sizeof(num));
d[s] = 0;
w[s] = weight[s];
num[s] = 1;
for(int i = 0; i < n; i ++) { //循环n次,每次选入一个顶点
int u = -1, MIN = INF;
for(int j = 0; j < n; j ++) {
if(vis[j] == false && d[j] < MIN) {
u = j;
MIN = d[j];
}
}
if(u == -1) return;
vis[u] = true;
for(int v = 0; v < n; v ++) {
if(vis[v] == false && G[u][v] != INF) {
if(d[u] + G[u][v] < d[v]) {
d[v] = d[u] + G[u][v];
w[v] = w[u] + weight[v];
num[v] = num[u]; //覆盖num[v]
}
else if(d[u] + G[u][v] == d[v]) {
if(w[u] + weight[v] > w[v]) {
w[v] = w[u] + weight[v];
}
num[v] += num[u];//最短路径条数与点权无关
}
}
}
}
}
int main() {
fill(G[0], G[0] + maxn * maxn, INF);
scanf("%d %d %d %d", &n, &m, &st, &ed);
for(int i = 0; i < n; i ++) {
scanf("%d", &weight[i]);
}
int u, v;
for(int i = 0; i < m; i ++) {
scanf("%d %d", &u, &v);
scanf("%d", &G[u][v]);
G[v][u] = G[u][v];
}
Dijkstra(st);
printf("%d %d\n", num[ed], w[ed]);
return 0;
}
A1018 Public Bike Management
题意:N + 1个点构成的图,管理中心编号为0。求最短路径,不唯一时求带出去车辆最少的路径,带出去车辆也相同时求带回车辆最少的路径。
解法:Dijkstra + DFS。技巧,设置minNeed和minRemain来记录带出去最少的数量和带回来最少的数量。由于每个点最佳数量为容量的一半,因此可以先将点权减去容量的一半,那么得到的数值就表示缺少的或多出来的车辆数量。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn = 510;
const int INF = 1000000000;
int n, m, Cmax, Sp, numPath = 0;
int G[maxn][maxn], weight[maxn];
int d[maxn], minNeed = INF, minRemain = INF;
bool vis[maxn] = {false};
vector<int> pre[maxn];
vector<int> tempPath, path;
void Dijkstra(int s) {
fill(d, d + maxn, INF);
d[s] = 0;
for(int i = 0; i < n; i ++) {
int u = 1, MIN = INF;
for(int j = 0; j <= n; j ++) { // n + 1个顶点
if(vis[j] == false && d[j] < MIN) {
u = j;
MIN = d[j];
}
}
if(u == -1) return;
vis[u] = true;
for(int v = 0; v <= n; v ++) {
if(vis[v] == false && G[u][v] != INF) {
if(d[u] + G[u][v] < d[v]) {
d[v] = d[u] + G[u][v];
pre[v].clear();
pre[v].push_back(u);
}
else if(d[u] + G[u][v] == d[v]) {
pre[v].push_back(u);
}
}
}
}
}
void DFS(int v) {
if(v == 0) {
tempPath.push_back(v);
int need = 0, remain = 0;
for(int i = tempPath.size() - 1; i >= 0; i --) {
int id = tempPath[i];
if(weight[id] > 0) {
remain += weight[id];
}
else {
if(remain > abs(weight[id])) {
remain -= abs(weight[id]);
}
else {
need += abs(weight[id]) - remain;
remain = 0;
}
}
}
if(need < minNeed) {
minNeed = need;
minRemain = remain;
path = tempPath;
}
else if(need == minNeed && remain < minRemain) {
minRemain = remain;
path = tempPath;
}
tempPath.pop_back();
return;
}
tempPath.push_back(v);
for(int i = 0; i < pre[v].size(); i ++) {
DFS(pre[v][i]);
}
tempPath.pop_back();
}
int main() {
scanf("%d %d %d %d", &Cmax, &n, &Sp, &m);
int u, v;
fill(G[0], G[0] + maxn * maxn, INF);
for(int i = 1; i <= n; i ++) {
scanf("%d", &weight[i]);
weight[i] -= Cmax / 2; //点权减去容量的一般
}
for(int i = 0; i < m; i ++) {
scanf("%d %d", &u, &v);
scanf("%d", &G[u][v]);
G[v][u] = G[u][v];
}
Dijkstra(0);
DFS(Sp);
printf("%d ", minNeed);
for(int i = path.size() - 1; i >= 0; i --) {
printf("%d", path[i]);
if(i > 0) printf("->");
}
printf(" %d", minRemain);
return 0;
}
A1030 Travel Plan
题意:求最短路,当路径不唯一时,求费用最小最短路。
解法:Dijkstra + DFS。注意cost数组也要初始化(相当于第二个d数组)
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn = 510;
const int INF = 1000000000;
int n, m, st, ed;
int G[maxn][maxn], cost[maxn][maxn], minCost = INF;
int d[maxn];
bool vis[maxn] = {false};
vector<int> pre[maxn];
vector<int> tempPath, path;
void Dijkstra(int s) {
fill(d, d + maxn, INF);
d[s] = 0;
for(int i = 0; i < n; i ++) {
int u = -1, MIN = INF;
for(int j = 0; j < n; j ++) {
if(vis[j] == false && d[j] < MIN) {
u = j;
MIN = d[j];
}
}
if(u == -1) return;
vis[u] = true;
for(int v = 0; v < n; v ++) {
if(vis[v] == false && G[u][v] != INF) {
if(d[u] + G[u][v] < d[v]) {
d[v] = d[u] + G[u][v];
pre[v].clear();
pre[v].push_back(u);
}
else if(d[u] + G[u][v] == d[v]) {
pre[v].push_back(u);
}
}
}
}
}
void DFS(int v) {
if(v == st) {
tempPath.push_back(v);
int tempCost = 0;
for(int i = tempPath.size() - 1; i > 0; i --) {
int id = tempPath[i], idNext = tempPath[i - 1];
tempCost += cost[id][idNext];
}
if(tempCost < minCost) {
minCost = tempCost;
path = tempPath;
}
tempPath.pop_back();
return;
}
tempPath.push_back(v);
for(int i = 0; i < pre[v].size(); i ++)
DFS(pre[v][i]);
tempPath.pop_back();
}
int main() {
fill(G[0], G[0] + maxn * maxn, INF);
fill(cost[0], cost[0] + maxn * maxn, INF);
int c1, c2;
scanf("%d %d %d %d", &n, &m, &st, &ed);
for(int i = 0; i < m; i ++) {
scanf("%d %d", &c1, &c2);
scanf("%d %d", &G[c1][c2], &cost[c1][c2]);
G[c2][c1] = G[c1][c2];
cost[c2][c1] = cost[c1][c2];
}
Dijkstra(st);
DFS(ed);
for(int i = path.size() - 1; i >= 0; i --) {
printf("%d ", path[i]);
}
printf("%d %d", d[ed], minCost);
return 0;
}
A1072 Gas Station
题意:一个图有两种节点,房屋和加油站且编号从1开始,所以Dijkstra算法的顶点范围是1到你+ m。求所有加油站中选择一个满足条件的最优加油站。要求到每个房屋的服务距离小于Ds,选出到房屋的最小距离最大的加油站,当存在多个这样的加油站则选出到房屋平均距离最小的加油站。
解法:多次调用Dijkstra计算最短路,选择最优的最短路。将加油站编号为n + 1 ~ n + m。遍历这些加油站,以当前加油站为起点调用Dijkstra,计算加油站到房屋的最短距离和平均距离,找出最短距离最大的加油站,不唯一则选择平均距离小的位置。
坑点:由于要多次调用Dijkstra,因此每次调用前需要将vis数组初始化为false。
房屋最大1000个,加油站最多10个,因此数组最小开到1011。
注意点:将输入的节点标识转换到1 ~ n + m。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 1010;
const int INF = 1000000000;
int n, m, k, Ds;
int G[maxn][maxn];
int d[maxn];
bool vis[maxn] = {false};
void Dijkstra(int s) {
memset(vis, false, sizeof(vis));
fill(d, d + maxn, INF);
d[s] = 0;
for(int i = 0; i < n + m; i ++) {
int u = -1, MIN = INF;
for(int j = 1; j <= n + m; j ++) {
if(vis[j] == false && d[j] < MIN) {
u = j;
MIN = d[j];
}
}
if(u == -1) return;
vis[u] = true;
for(int v = 1; v <= n + m; v ++) {
if(vis[v] == false && G[u][v] != INF) {
if(d[u] + G[u][v] < d[v]) {
d[v] = d[u] + G[u][v];
}
}
}
}
}
int getId(char str[]) {
int i = 0, len = strlen(str), id = 0;
while(i < len) {
if(str[i] != 'G') {
id = id * 10 + (str[i] - '0');
}
i ++;
}
if(str[0] == 'G') return n + id;
else return id;
}
int main() {
fill(G[0], G[0] + maxn * maxn, INF);
scanf("%d %d %d %d", &n, &m, &k, &Ds);
int u, v, w;
char city1[5], city2[5];
for(int i = 0; i < k; i ++) {
scanf("%s %s %d", city1, city2, &w);
u = getId(city1);
v = getId(city2);
G[u][v] = G[v][u] = w;
}
double ansDis = -1, ansAvg = INF;
int ansId = -1;
for(int i = n + 1; i <= n + m; i ++) { //枚举所有加油站,从所有加油站开时最短路
double minDis = INF, avg = 0;
Dijkstra(i);
for(int j = 1; j <= n; j ++) {
if(d[j] > Ds) { //不符合要求
minDis = -1;
break;
}
if(d[j] < minDis) minDis = d[j];
avg += 1.0 * d[j] / n;
}
if(minDis == -1) continue;
if(minDis > ansDis) {
ansId = i;
ansDis = minDis;
ansAvg = avg;
}
else if(minDis == ansDis && avg < ansAvg) {
ansId = i;
ansAvg = avg;
}
}
if(ansId == -1) printf("No Solution\n");
else {
printf("G%d\n", ansId - n);
printf("%.1f %.1f\n", ansDis, ansAvg);
}
return 0;
}
A1087 All Roads Lead to Rome
题意:最大点权最短路(最小费用)。当最大点权相同时,求平均点权最大的路径。
解法:DIjkstra + DFS。先Dijkstra活动最短路径网络,然后DFS遍历路径网络,选出最大点权或平均点权最大的路径。
#include<iostream>
#include<map>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn = 210;
const int INF = 1000000000;
int G[maxn][maxn], weight[maxn];
int d[maxn], numPath = 0, maxW = 0;
double maxAvg = 0;
bool vis[maxn];
int n, k, st;
vector<int> pre[maxn];
vector<int> tempPath, path;
map<string, int> city2id;
map<int, string> id2city;
void Dijkstra(int s) {
fill(d, d + maxn, INF);
d[s] = 0;
for(int i = 0; i < n; i ++) {
int u = -1, MIN = INF;
for(int j = 0; j < n; j ++) {
if(vis[j] == false && d[j] < MIN) {
u = j;
MIN = d[j];
}
}
if(u == -1) return;
vis[u] = true;
for(int v = 0; v < n; v ++) {
if(vis[v] == false && G[u][v] != INF) {
if(d[u] + G[u][v] < d[v]) {
d[v] = d[u] + G[u][v];
pre[v].clear();
pre[v].push_back(u);
}
else if(d[u] + G[u][v] == d[v]) {
pre[v].push_back(u);
}
}
}
}
}
void DFS(int v) {
if(v == st) {
tempPath.push_back(v);
numPath ++;
int tempW = 0;
for(int i = tempPath.size() - 1; i >= 0; i --) {
int id = tempPath[i];
tempW += weight[id];
}
double tempAvg = 1.0 * tempW / (tempPath.size() - 1);
if(tempW > maxW) {
maxW = tempW;
maxAvg = tempAvg;
path = tempPath;
}
else if(tempW == maxW && tempAvg > maxAvg) {
maxAvg = tempAvg;
path = tempPath;
}
tempPath.pop_back();
return;
}
tempPath.push_back(v);
for(int i = 0; i < pre[v].size(); i ++)
DFS(pre[v][i]);
tempPath.pop_back();
}
int main() {
fill(G[0], G[0] + maxn * maxn, INF);
string start, city1, city2;
cin >> n >> k >> start;
city2id[start] = 0;
id2city[0] = start;
for(int i = 1; i <= n - 1; i ++) {
cin >> city1 >> weight[i];
city2id[city1] = i;
id2city[i] = city1;
}
for(int i = 0; i < k; i ++) {
cin >> city1 >> city2;
int c1 = city2id[city1], c2 = city2id[city2];
cin >> G[c1][c2];
G[c2][c1] = G[c1][c2];
}
Dijkstra(0);
int rom = city2id["ROM"];
DFS(rom);
printf("%d %d %d %d\n", numPath, d[rom], maxW, (int)maxAvg);
for(int i = path.size() - 1; i >= 0; i --) {
printf("%s", id2city[path[i]].c_str());
if(i > 0) printf("->");
}
return 0;
}
A1111 Online Map
题意:求最短路径中的最快路径以及求最快路径的最少节点路径。
解法:两次Dijkstra + DFS。
注意点:两次调用Dijkstra最后直接在两张图上进行,避免d数组和cost数组相互影响。不要忘记设置vis[u] = true。
找到了上次一直错的原因:首先写成memset(vis, sizeof(vis), false)了。第二,Dijkstra和DFS的调用顺序搞错了。
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 510;
const int INF = 1000000000;
int n, m, st, ed;
int G[maxn][maxn], time[maxn][maxn];
int d[maxn], t[maxn];
bool vis[maxn] = {false};
vector<int> pre1[maxn], pre2[maxn];
vector<int> tempPath1, path1, tempPath2, path2;
int minTime = INF, minSize = INF;
void Dijkstra1(int s) {
fill(d, d + maxn, INF);
memset(vis, false, sizeof(vis));
d[s] = 0;
for(int i = 0; i < n; i ++) {
int u = -1, MIN = INF;
for(int j = 0; j < n; j ++) {
if(vis[j] == false && d[j] < MIN) {
u = j;
MIN = d[j];
}
}
if(u == -1) return;
vis[u] = true;
for(int v = 0; v < n; v ++) {
if(vis[v] == false && G[u][v] != INF) {
if(d[u] + G[u][v] < d[v]) {
d[v] = d[u] + G[u][v];
pre1[v].clear();
pre1[v].push_back(u);
}
else if(d[u] + G[u][v] == d[v]) {
pre1[v].push_back(u);
}
}
}
}
}
void Dijkstra2(int s) {
fill(t, t + maxn, INF);
memset(vis, false, sizeof(vis));
t[s] = 0;
for(int i = 0; i < n; i ++) {
int u = -1, MIN = INF;
for(int j = 0; j < n; j ++) {
if(vis[j] == false && t[j] < MIN) {
u = j;
MIN = t[j];
}
}
if(u == -1) return;
vis[u] = true;
for(int v = 0; v < n; v ++) {
if(vis[v] == false && time[u][v] != INF) {
if(t[u] + time[u][v] < t[v]) {
t[v] = t[u] + time[u][v];
pre2[v].clear();
pre2[v].push_back(u);
}
else if(t[u] + time[u][v] == t[v]) {
pre2[v].push_back(u);
}
}
}
}
}
void DFS1(int v) {
if(v == st) {
tempPath1.push_back(v);
int tempTime = 0;
for(int i = tempPath1.size() - 1; i > 0; i --) {
int id = tempPath1[i], idNext = tempPath1[i - 1];
tempTime += time[id][idNext];
}
if(tempTime < minTime) {
minTime = tempTime;
path1 = tempPath1;
}
tempPath1.pop_back();
return;
}
tempPath1.push_back(v);
for(int i = 0; i < pre1[v].size(); i ++)
DFS1(pre1[v][i]);
tempPath1.pop_back();
}
void DFS2(int v) {
if(v == st) {
tempPath2.push_back(v);
int tempSize = tempPath2.size();
if(tempSize < minSize) {
minSize = tempSize;
path2 = tempPath2;
}
tempPath2.pop_back();
return;
}
tempPath2.push_back(v);
for(int i = 0; i < pre2[v].size(); i ++)
DFS2(pre2[v][i]);
tempPath2.pop_back();
}
int main() {
fill(G[0], G[0] + maxn * maxn, INF);
fill(time[0], time[0] + maxn * maxn, INF);
scanf("%d %d", &n, &m);
int u, v, oneWay, L, T;
for(int i = 0; i < m; i ++) {
scanf("%d %d %d %d %d", &u, &v, &oneWay, &L, &T);
if(oneWay == 1) {
G[u][v] = L;
time[u][v] = T;
}
else {
G[u][v] = G[v][u] = L;
time[u][v] = time[v][u] = T;
}
}
scanf("%d %d", &st, &ed);
Dijkstra1(st);
DFS1(ed);
Dijkstra2(st);
DFS2(ed);
if(path1 == path2) {
printf("Distance = %d; Time = %d: ", d[ed], t[ed]);
for(int i = path1.size() - 1; i >= 0; i --) {
printf("%d", path1[i]);
if(i > 0) printf(" -> ");
}
}
else {
printf("Distance = %d: ", d[ed]);
for(int i = path1.size() - 1; i >= 0; i --) {
printf("%d", path1[i]);
if(i > 0) printf(" -> ");
}
printf("\nTime = %d: ", t[ed]);
for(int i = path2.size() - 1; i >= 0; i --) {
printf("%d", path2[i]);
if(i > 0) printf(" -> ");
}
}
return 0;
}
图的判断
A1122 Hamiltonian Cycle
题意:判断路径是否是哈密尔顿环。
解法:哈密尔顿环是除起点外只包含图中所有顶点一次的简单环路。
判断逻辑如下:(1)路径长度不等于n + 1,false; (2)路径首尾不等,false;(3)路径上的边不存在,false(4)用一个hashTable统计路径中节点出现次数,除起点外出现次数大于1,false;
(5)其余情况,true。
坑点:注意定义数组时,先定义长度变量len,但此时一定要读入这个len,否则没法申请数组path[len]。
#include<cstdio>
const int maxn = 210;
int G[maxn][maxn] = {0};
bool vis[maxn] = {false};
int n, m, k;
bool isHamiltonianCircle(int path[], int len) {
if(len != n + 1) {
return false;
}
if(path[0] != path[len - 1]) {
return false;
}
int hashTable[len + 1] = {0};
for(int i = 0; i < len - 1; i ++) {
if(G[path[i]][path[i + 1]] == 0) {
return false;
}
hashTable[path[i]] ++;
}
for(int i = 0; i < len - 1; i ++)
if(hashTable[path[i]] > 1) {
return false;
}
return true;
}
int main() {
scanf("%d %d", &n, &m);
int u, v;
for(int i = 0; i < m; i ++) {
scanf("%d %d", &u, &v);
G[u][v] = G[v][u] = 1;
}
scanf("%d", &k);
int len;
for(int i = 0; i < k; i ++) {
scanf("%d", &len);
int path[len];
for(int j = 0; j < len; j ++)
scanf("%d", &path[j]);
if(isHamiltonianCircle(path, len)) printf("YES\n");
else printf("NO\n");
}
return 0;
}
A1126 Eulerian Path
题意:判断图中是否存在是欧拉图,半欧拉图,非欧拉图。
分析:欧拉图:存在欧拉路径,即所有顶点的度均为偶数。
半欧拉图:只有两个顶点的度为奇数,其余均为偶数。
非欧拉图:超过两个顶点的度为奇数。
坑点:如果图不连通,则为非欧拉图。因此写一个DFS来判断是否连通。需要邻居矩阵G,vis数组。然后遍历图,统计遍历到的顶点数cnt,如果cnt不等于n,则是非欧拉图,否则继续判断。
#include<cstdio>
const int maxn = 510;
int G[maxn][maxn] = {0};
bool vis[maxn] = {false};
int degree[maxn] = {0};
int n, m;
void judgeEulerian(int degree[], int n) {
int oddNum = 0;
for(int i = 1; i <= n; i ++) {
if(degree[i] % 2 == 1) oddNum ++;
}
if(oddNum == 2) printf("Semi-Eulerian\n");
else if(oddNum == 0) printf("Eulerian\n");
else printf("Non-Eulerian\n");
}
int cnt = 0;
void DFS(int u) {
vis[u] = true;
cnt ++;
for(int v = 1; v <= n; v ++) {
if(vis[v] == false && G[u][v] != 0)
DFS(v);
}
}
int main() {
scanf("%d %d", &n, &m);
int u, v;
for(int i = 0; i < m; i ++) {
scanf("%d %d", &u, &v);
G[u][v] = G[v][u] = 1;
degree[u] ++;
degree[v] ++;
}
for(int i = 1; i <= n; i ++) {
printf("%d", degree[i]);
if(i < n) printf(" ");
else printf("\n");
}
DFS(1);
if(cnt != n) printf("Non-Eulerian\n");
else judgeEulerian(degree, n);
return 0;
}
A1134 Vertex Cover
题意:判断给定顶点集是否是图的顶点覆盖。顶点覆盖要求图中所有边都和顶点集中的某个顶点有关。
分析:只需将边存储起来,然后遍历,在顶点集中寻找边的两端,如果两端都找不到,则返回false,当所有边都通过测试则返回true。
#include<cstdio>
#include<set>
using namespace std;
const int maxn = 10010;
int n, m, k;
struct Edge {
int u, v;
}edge[maxn];
bool isVertexCover(set<int> s) {
for(int i = 0; i < m; i ++) {
if(s.find(edge[i].u) == s.end() && s.find(edge[i].v) == s.end())
return false;
}
return true;
}
int main() {
scanf("%d %d", &n, &m);
int u, v;
for(int i = 0; i < m; i ++) {
scanf("%d %d", &edge[i].u, &edge[i].v);
}
scanf("%d", &k);
for(int i = 0; i < k; i ++) {
set<int> s;
int Nv;
scanf("%d", &Nv);
for(int j = 0; j < Nv; j ++) {
scanf("%d", &v);
s.insert(v);
}
if(isVertexCover(s)) printf("Yes\n");
else printf("No\n");
}
return 0;
}
A1139 First Contact
题意:A和B是lovers, 找到一对C和D连接A和B,要求A和C同性,B和D同性,性别女由负号表示。
解法:遍历。遍历A的同性朋友C和B的同性朋友D,(避免遍历C直接和B,D直接和A相连),如果C和D是朋友,将这对C和D存入答案集合中。
注意:不能用正负相乘和零比较的方法判断是否同性,因为数据中可能存在0000和-0000的数据。可以简单的通过字符串长度判断。答案放在set<pair<int, int>>中可以直接有序。一开始先遍历A的朋友C,再遍历C的朋友D然后判断D是否是B的朋友,但是出错。后来发现这样也可以,只要判断逻辑和上面的方法一致。
#include<cstdio>
#include<iostream>
#include<set>
#include<map>
using namespace std;
const int maxn = 310;
int n, m, k;
map<string, set<string>> G;
int main() {
scanf("%d %d", &n, &m);
string a, b;
for(int i = 0; i < m; i ++) {
cin >> a >> b;
G[a].insert(b);
G[b].insert(a);
}
scanf("%d", &k);
for(int i = 0; i < k; i ++) {
cin >> a >> b;
set<pair<int, int>> v;
for(auto c : G[a]) { //a的朋友中
for(auto d : G[b]) {
if(d == a || c == b) continue;
if(a.length() == c.length() && b.length() == d.length() && G[c].find(d) != G[c].end())
v.insert(make_pair(abs(stoi(c)), abs(stoi(d))));
}
}
printf("%d\n", v.size());
for(auto it : v) {
printf("%04d %04d\n", it.first, it.second);
}
}
return 0;
}
A1142 Maximal Clique
题意:判断给定顶点集是否是团,是团的话是否是最大团。
解法:首先判断顶点集是否是团,只需判断两两之间是否都有边相连,一旦出现一个没有边的则不是环,自己和自己判断需要跳过。
当判断是团的情况下,再判断是否是最大环,判断方法是:在团的顶点集之外的顶点中寻找有没有一个顶点和团内所有顶点都相连,如果存在则说明团不是最大团,如果遍历完所有团外顶点都没找到,则是最大团。
坑点:在判断两两顶点是否相连时,加上一个自环来避免自己和自己的判断是行不通的。即修正后不加自环,判断是否是团时,如果it1和it2相同则跳过,才是正确的解法,用自环替代是不可行的。
#include<cstdio>
#include<set>
using namespace std;
const int maxn = 210;
int G[maxn][maxn] = {0};
int Nv, Ne, M, K;
bool isClique(set<int> s) {
for(auto it1 : s) {
for(auto it2 : s) {
if(it1 == it2) continue;
if(G[it1][it2] == 0) return false; //not clique
}
}
return true;
}
bool isMaxClique(set<int> s) {
for(int i = 1; i <= Nv; i ++) { // for all vertex
if(s.find(i) == s.end()) { //if not in clique
bool flag = true;
for(auto it : s) { // for every vertex in clique
if(G[i][it] == 0) { //the vertex has an edge with it
flag = false;
}
}
if(flag) return false; //not maxClique
}
}
return true;
}
int main() {
scanf("%d %d", &Nv, &Ne);
int u, v;
for(int i = 1; i <= Ne; i ++) {
scanf("%d %d", &u, &v);
G[u][v] = G[v][u] = 1;
}
scanf("%d", &M);
for(int i = 0; i < M; i ++) {
scanf("%d", &K);
set<int> s;
for(int j = 0; j < K; j ++) {
scanf("%d", &v);
s.insert(v);
}
if(isClique(s)) {
if(isMaxClique(s)) printf("Yes\n");
else printf("Not Maximal\n");
}
else printf("Not a Clique\n");
}
return 0;
}
A1146 Topological Order
题意:给定图和序列,判断给定序列是否是拓扑序。将不是拓扑序的查询编号输出。
解法:使用邻接表edge。记录每个顶点的入度。如果度为0,则可以选择当前节点。具体实现上,遍历序列,判断序列当前元素是否是度为0的顶点,是的话继续遍历,将该顶点删除,并将连着的边删除,将被连的顶点度-1。
注意点:由于需要多次判断,因此将度数组保存起来,用临时数组进行判断,每次判断前复制过去。要讲连着的顶点的度减1,只需用edge记录某个顶点的所有后继顶点,然后遍历这个edge链表,删除时将这些后继顶点的度-1。
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int maxn = 1010;
const int maxm = 10010;
int degree[maxn] = {0}, tempDegree[maxn] = {0};
int n, m, k;
vector<int> edge[maxm];
int main() {
scanf("%d %d", &n, &m);
int u, v;
for(int i = 0; i < m; i ++) {
scanf("%d %d", &u, &v);
degree[v] ++;
edge[u].push_back(v);
}
scanf("%d", &k);
vector<int> ans;
for(int i = 0; i < k; i ++) {
int seq[n];
for(int j = 0; j < n; j ++)
scanf("%d", &seq[j]);
memcpy(tempDegree, degree, sizeof(degree));
bool flag = true;
for(int j = 0; j < n; j ++) {
if(tempDegree[seq[j]] == 0) {
for(int s = 0; s < edge[seq[j]].size(); s ++) {
tempDegree[edge[seq[j]][s]] --;
}
}
else {
flag = false;
}
}
if(flag == false) ans.push_back(i);
}
for(int i = 0; i < ans.size(); i ++) {
printf("%d", ans[i]);
if(i < ans.size() - 1) printf(" ");
}
return 0;
}
A1150 Travelling Salesman Problem
题意:判断路径是否是旅行售货员问题的解。旅行售货员问题的解,使用找到一条路径经过每个城市(顶点)并回到源点(即哈密尔顿回路)。但可以顶点可以重复,不重复的叫TS simple circle。
坑点:首先将路径读入数组path中,然后计算路径长度,最后进行判断输出。注意判断逻辑的完整性。整体判断逻辑如下:如果路径中存在不可达的边,则Not a TS cycle; 如果路径首尾不同,则不是环;如果路径经过的城市数小于总城市数n + 1(包括终点),则Not a TS cycle;城市数大于或等于n+1则存在解的可能,此时需要进一步判断是否每一个城市都走过,用局部变量hashTable实现,只有每个城市都至少去过一遍的情况下,才是解,在这些解中记录最短路径长度和标号。
#include<cstdio>
#include<cstring>
const int maxn = 210;
const int INF = 1000000000;
int n, m, k;
int G[maxn][maxn] = {0};
int main() {
scanf("%d %d", &n, &m);
int u, v, dis;
for(int i = 0; i < m; i ++) {
scanf("%d %d %d", &u, &v, &dis);
G[u][v] = G[v][u] = dis;
}
scanf("%d", &k);
int numCity, shortestDist = INF, ansIdx = -1;
for(int i = 1; i <= k; i ++) {
scanf("%d", &numCity);
int path[numCity];
int hashTable[n + 1] = {0};
// memset(hashTable, 0, sizeof(hashTable));
int totalDist = 0;
for(int j = 0; j < numCity; j ++) {
scanf("%d", &path[j]);
hashTable[path[j]] ++;
}
for(int j = 0; j < numCity - 1; j ++) {
if(G[path[j]][path[j + 1]] == 0) {
totalDist = -1;
break;
}
totalDist += G[path[j]][path[j + 1]];
}
printf("Path %d: ", i);
if(totalDist == -1) printf("NA ");
else printf("%d ", totalDist);
if(totalDist == -1) {
printf("(Not a TS cycle)\n");
}
else if(path[0] != path[numCity - 1]) {
printf("(Not a TS cycle)\n");
}
else if(numCity < n + 1) {
printf("(Not a TS cycle)\n");
}
else if(numCity > n + 1) {
bool flag = true;
for(int s = 1; s <= n; s ++) {
if(hashTable[s] == 0) {
flag = false;
break;
}
}
if(flag == false) printf("(Not a TS cycle)\n");
else {
printf("(TS cycle)\n");
if(totalDist < shortestDist) {
shortestDist = totalDist;
ansIdx = i;
}
}
}
else if(numCity == n + 1) {
bool flag = true;
for(int s = 1; s <= n; s ++) {
if(hashTable[s] == 0) {
flag = false;
break;
}
}
if(flag == false) printf("(Not a TS cycle)\n");
else {
printf("(TS simple cycle)\n");
if(totalDist < shortestDist) {
shortestDist = totalDist;
ansIdx = i;
}
}
}
}
printf("Shortest Dist(%d) = %d", ansIdx, shortestDist);
return 0;
}
A1154 Vertex Coloring
题意:给定图结构和顶点的颜色,判断是否满足着色方案,即相邻的顶点颜色不同,若可着色,计算颜色数。
解法:记录边和顶点颜色。遍历边判断两端顶点颜色是否相同,存在相同则输出No,都满足则计算顶点颜色数。一开始使用hashTable统计颜色数,会段错误,int范围太大了。因此改成map来统计,map.size()便是颜色数。注意每次记得清空map。
#include<cstdio>
#include<map>
using namespace std;
const int maxn = 10010;
int n, m, k;
int vertex[maxn];
struct Edge {
int u, v;
} edge[maxn];
map<int, int> mp;
int main() {
scanf("%d %d", &n, &m);
int u, v;
for(int i = 0; i < m; i ++) {
scanf("%d %d", &edge[i].u, &edge[i].v);
}
scanf("%d", &k);
for(int i = 0; i < k; i ++) {
for(int j = 0; j < n; j ++)
scanf("%d", &vertex[j]);
bool flag = true;
for(int j = 0; j < m; j ++) {
if(vertex[edge[j].u] == vertex[edge[j].v]) {
flag = false;
break;
}
}
if(flag == false) printf("No\n");
else {
for(int j = 0; j < n; j ++) {
mp[vertex[j]] ++;
}
printf("%d-coloring\n", mp.size());
mp.clear();
}
}
return 0;
}