1003 Emergency (25 分)
- 题意 :求无向图中最短路的数量,以及在最短路情况下,点权之和最大是多少
- 思路 :dijkstra的扩展一般在第三步“用t更新其它点“;spfa不能用来求最短路的数量(因为每个点可能被更新多次);假设有三个点可以走到终点x,那么从起点s走到x的路径分为三类,s->1->x…,如果d1<d2<d3,最短距离的数量就是走到第一个点的数量,如果d1=d2<d3,前两个相加,同理,求最短路情况下点权和最大,就是三者取max(如果三者相同);cnt数组记录从起点走到i的最短路径数量;sum数组记录从起点到i的最大点权
- 语法 :如果点数<=1000,邻接矩阵就没有问题,>=1e4就一定要邻接表存;
#include <iostream>
#include <cstring>
using namespace std;
const int N = 510;
int n, m, S, T;
int w[N], g[N][N];
int cnt[N], sum[N];
int dist[N];
bool st[N];
void dijkstra()
{
memset(dist, 0x3f, sizeof dist); // 初始化
dist[S] = 0, cnt[S] = 1, sum[S] = w[S];
for (int i = 0; i < n - 1; i ++ )
{
int t = -1;
for (int j = 0; j < n; j ++ )
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
st[t] = true; // 标记
for (int j = 0; j < n; j ++ )
if (dist[j] > dist[t] + g[t][j])
{
dist[j] = dist[t] + g[t][j];
cnt[j] = cnt[t];
sum[j] = sum[t] + w[j];
}
else if (dist[j] == dist[t] + g[t][j])
{
cnt[j] += cnt[t];
sum[j] = max(sum[j], sum[t] + w[j]);
}
}
}
int main()
{
cin >> n >> m >> S >> T;
for (int i = 0; i < n; i ++ ) cin >> w[i];
memset(g, 0x3f, sizeof g); // 初始化
while (m -- )
{
int a, b, c;
cin >> a >> b >> c;
g[a][b] = g[b][a] = min(g[a][b], c); // 无向图
}
dijkstra();
cout << cnt[T] << ' ' << sum[T];
return 0;
}
- 思路 :dijkstra中在循环for或者while的外面初始化时,起点的st不要设成true!堆优化版在一个点被更新松弛后将它放入优先队列内
- 语法 :堆优化版写成链式前向星,M开成N*N
// 堆优化版
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 510, M = N * N; // 注意M大小
typedef pair<int, int> pii;
int h[N], w[M], e[M], ne[M], idx;
int wt[N];
int dist[N];
bool st[N];
int cnt[N], sum[N];
int n, m, S, T;
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[S] = 0;
cnt[S] = 1, sum[S] = wt[S];
priority_queue<pii, vector<pii>, greater<pii>> heap;
heap.push({0, S});
// st[S] = true; 注意!!!
while (heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.second, distance = t.first;
if (st[ver]) continue;
st[ver] = true;
for (int i = h[ver]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[ver] + w[i])
{
dist[j] = dist[ver] + w[i];
cnt[j] = cnt[ver];
sum[j] = sum[ver] + wt[j];
heap.push({dist[j], j}); // 被更新松弛了的点要放入优先队列内
}
else if (dist[j] == dist[ver] + w[i])
{
cnt[j] += cnt[ver];
sum[j] = max(sum[j], sum[ver] + wt[j]);
}
}
}
}
int main()
{
memset(h, -1, sizeof h);
cin >> n >> m >> S >> T;
for (int i = 0; i < n; i ++ ) cin >> wt[i];
while (m -- )
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
add(b, a, c);
}
dijkstra();
cout << cnt[T] << ' ' << sum[T];
return 0;
}
1018 Public Bike Management (30 分)
题意 :
- 给一个终点,要从起点出发,要找到一条路径,第一优先级的要求是 距离最短;如果有很多条距离相同的路径,第二优先级,最初带去的自行车数量越少越好;第三优先级,最终带回的自行车数量越少越好
思路 :
- 多关键字的dijk,但前面的问题是可以在dijk过程中去满足,这题比较特殊,后面两个要求是不能再dijk过程中去满足的;比如从两个点中选一个去第三个点,第一个点出发要带5辆车,第二个点出发要带6辆车,但我们无法分析到底哪个点更好,比如到第三个点恰好缺5辆,第一个点就更好,但也有可能缺6辆,第二个点就更好。像以前的关键字都是累加的,第一关键字距离第二关键字时间,时间肯定越短越好,但这题无法判断
- 所以这题就比较复杂,先做一遍dijk,求每个点到终点的最短距离,然后再去把所有从起点到终点的最短距离搜索一遍,从所有的最短路径中找后两个条件最好的
- 这题本应是指数级复杂度,但对于一个随机图而言,最短路径不会太多
- 求dijk是为了剪枝,保证走的时候是最短路径;比如我们当前在u点,要判断u点走到v点有没有可能使是最短路径呢?可以用dist数组判断,如果
dist[u] = dist[u -> v] + dist[v]
,那么说明我们搜的时候可以从u走到v,这其实就是一个剪枝 - 用一个变量s表示当前需要带去多少自行车,可以是负数,在过程中,负数的最小值就是我们需要带去的自行车;带回的自行车是多少呢?就是看最终s的值是多少,再加上带去的数量
- 爆搜时用一个vector容器 的 path来存路径,ans来存答案路径
- 当爆搜到终点时,在计算需要带的数量sd时,此时的mins有可能为正数,也就是一辆都不用带,所以要和0取最小的,最终需要带走的数量bg就是s加上sd
- 注意这个mins不是全局变量!!是当前这条路径上的最小值
- dfs时,假设放了,之后还要回溯pop_back
语法 :
s -= (C + 1) / 2 - c[u];
是上取整- 带权无向图,首先初始化g数组为无穷;带权,未说明是否有重边,因此取min
- 初始时,dfs前,不要忘了往里面加上起点0
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
#define pb push_back
const int N = 510, INF = 0x3f3f3f3f;
int C, n, S, m;
int c[N];
int g[N][N];
int dist[N];
bool st[N];
vector<int> path, ans;
int send = INF, bring = INF;
void dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[S] = 0;
for (int i = 0; i < n; i ++ )
{
int t = -1;
for (int j = 0; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
st[t] = true;
for (int j = 0; j <= n; j ++ )
dist[j] = min(dist[j], dist[t] + g[t][j]);
}
}
void dfs(int u, int s, int mins)
{
if (u)
{
s -= (C + 1) / 2 - c[u];
mins = min(mins, s);
}
if (u == S)
{
int sd = abs(min(0, mins));
int bg = sd + s;
if (sd < send || (sd == send && bring > bg))
ans = path, send = sd, bring = bg;
return ;
}
for (int i = 1; i <= n; i ++ )
if (dist[u] == dist[i] + g[u][i])
{
path.pb(i);
dfs(i, s, mins);
path.pop_back();
}
}
int main()
{
scanf("%d%d%d%d", &C, &n, &S, &m);
for (int i = 1; i <= n && scanf("%d", &c[i]); i ++ );
memset(g, 0x3f, sizeof g);
for (int i = 0, x, y, z; i < m && scanf("%d%d%d", &x, &y, &z); i ++ )
g[x][y] = g[y][x] = min(g[x][y], z);
dijkstra();
path.pb(0);
dfs(0, 0, 0);
printf("%d 0", send);
for (int i = 1; i < ans.size(); i ++ ) printf("->%d", ans[i]);
printf(" %d", bring);
}
1122 Hamiltonian Cycle (25 分)
题意 :
- Hamiltonian Cycle哈密顿回路
- 哈密顿回路问题是找到一个包含图中每个顶点的简单回路。
- 在本题中,你需要做的是判断给定路径是否为哈密顿回路。
- 无向图
思路 :
- 恰好每个点走一次,且第一个点和最后一个点是同一个点
- 如果满足了以上两个要求,就按它给的路径走一遍,如果从当前这个点都能走到下一个点,且所有点都走了一次,就是哈密顿路径
- 这题有一个很坑的地方,题目只说了图中点的数量是200,但没说输入的路径的点数也是200,所以如果路径数组开太小,会有一个点过不了
#include <iostream>
using namespace std;
const int N = 210;
int n, m;
int nodes[N * 2], cnt;
bool g[N][N];
bool st[N];
bool check()
{
if (cnt != n + 1 || nodes[0] != nodes[cnt - 1]) return false;
for (int i = 1; i <= n; i ++ ) st[i] = 0;
for (int i = 0; i < cnt - 1; i ++ )
{
st[nodes[i]] = true;
if (!g[nodes[i]][nodes[i + 1]])
return false;
}
for (int i = 1; i <= n; i ++ )
if (!st[i])
return false;
return true;
}
int main()
{
scanf("%d%d", &n, &m);
int a, b;
while (m -- )
{
scanf("%d%d", &a, &b);
g[a][b] = g[b][a] = true;
}
int k; scanf("%d", &k);
while (k -- )
{
scanf("%d", &cnt);
for (int i = 0; i < cnt; i ++ ) scanf("%d", &nodes[i]);
if (check()) puts("YES");
else puts("NO");
}
}
1126 Eulerian Path (25 分)
题意 :
- Eulerian Path欧拉路径
- circuit环路;degree度数
- 欧拉路径是图中的一条路径,该路径满足恰好访问每个边一次。
- 欧拉回路是一条在同一顶点处开始和结束的欧拉路径。
- 如果一个连通图的所有顶点的度数都为偶数,那么这个连通图具有欧拉回路,且这个图被称为欧拉图
- 如果一个连通图中有两个顶点的度数为奇数,其他顶点的度数为偶数,那么所有欧拉路径都从其中一个度数为奇数的顶点开始,并在另一个度数为奇数的顶点结束。
- 具有欧拉路径但不具有欧拉回路的图被称为半欧拉图。
- 现在,给定一个无向图,请你判断它是欧拉图、半欧拉图还是非欧拉图。
思路 :
- 每个点的度数是什么呢?就是这个点连的边数
- 首先不管是欧拉图还是半欧拉图都必须是连通的;所以第一步是判断连通性,从某个点出发,看能否把所有点搜到,dfs或者bfs;这个判断连通性是 图的遍历,时间复杂度为 点数加边数
- 然后判断度数,直接按照定义即可
- 判断连通性的写法 :从1号点出发,看能搜到几个点,遍历的时候为了避免重复搜索,要有一个判重数组
#include <iostream>
using namespace std;
const int N = 510;
int n, m;
bool g[N][N];
int d[N];
bool st[N]; // dfs用
int dfs(int u)
{
st[u] = true;
int cnt = 1;
for (int i = 1; i <= n; i ++ )
{
if (!st[i] && g[u][i])
cnt += dfs(i);
}
return cnt;
}
int main()
{
scanf("%d%d", &n, &m);
int a, b;
while (m -- )
{
scanf("%d%d", &a, &b);
g[a][b] = g[b][a] = true;
d[a] ++ , d[b] ++ ;
}
int cnt = dfs(1);
printf("%d", d[1]);
for (int i = 2; i <= n; i ++ ) printf(" %d", d[i]);
puts("");
if (cnt == n)
{
int s = 0;
for (int i = 1; i <= n; i ++ )
if (d[i] % 2)
s ++ ;
if (!s) puts("Eulerian");
else if (s == 2) puts("Semi-Eulerian");
else puts("Non-Eulerian");
}
else
puts("Non-Eulerian");
}
1134 Vertex Cover (25 分)
题意 :
- 如果图中的一个顶点集合能够满足图中的每一条边都至少有一个端点在该集合内,那么这个顶点集合就是图的顶点覆盖。
- 现在给定一张图,以及若干个顶点集合,请你判断这些顶点集合是否是图的顶点覆盖。
思路 :
- 直接模拟即可
#include <iostream>
#include <cstring>
using namespace std;
const int N = 10010;
int n, m;
struct Edge
{
int a, b;
}e[N];
bool st[N];
int main()
{
cin >> n >> m;
for (int i = 0; i < m; i ++ ) cin >> e[i].a >> e[i].b;
int k;
cin >> k;
while (k -- )
{
int cnt;
cin >> cnt;
memset(st, 0, sizeof st);
while (cnt -- )
{
int x;
cin >> x;
st[x] = true;
}
int i;
for (i = 0; i < m; i ++ )
if (!st[e[i].a] && !st[e[i].b])
break;
if (i == m) puts("Yes");
else puts("No");
}
return 0;
}
1142 Maximal Clique (25 分)
题意 :
- 在一个无向图中,如果一个顶点子集满足子集内的任意两个不同顶点之间都是相连的,那么这个顶点子集就被称为一个团。
- 如果一个团不能通过加入某个新的顶点来扩展成一个更大的团,那么该团就被称为最大团。
- 现在,你需要判断给定顶点子集能否构成一个最大团。
#include <iostream>
#include <cstring>
using namespace std;
const int N = 210;
int n, m;
bool g[N][N];
int nodes[N], cnt;
bool st[N];
bool check_clique()
{
for (int i = 0; i < cnt; i ++ )
for (int j = 0; j < i; j ++ )
if (!g[nodes[i]][nodes[j]])
return false;
return true;
}
bool check_maximum()
{
memset(st, 0, sizeof st);
for (int i = 0; i < cnt; i ++ )
st[nodes[i]] = true;
for (int i = 1; i <= n; i ++ )
{
if (!st[i])
{
bool success = false;
for (int j = 0; j < cnt; j ++ )
if (!g[i][nodes[j]])
{
success = true;
break;
}
if (!success) return false;
}
}
return true;
}
int main()
{
scanf("%d%d", &n, &m);
int a, b;
while (m -- )
{
scanf("%d%d", &a, &b);
g[a][b] = g[b][a] = true;
}
int k; scanf("%d", &k);
while (k -- )
{
scanf("%d", &cnt);
for (int i = 0; i < cnt; i ++ ) scanf("%d", &nodes[i]);
if (check_clique())
{
if (check_maximum()) puts("Yes");
else puts("Not Maximal");
}
else
puts("Not a Clique");
}
}
1146 Topological Order (25 分)
题意 :
- 以下哪个选项不是从给定的有向图中获得的拓扑序列?
- 现在,请你编写一个程序来测试每个选项。
思路 :
- 将输入的每一条边存下来
- 对于每对询问,遍历每条边,如果边的起点比边的终点在询问中出现的位置晚,说明询问不合格
语法 :
- is_first的使用,如果是true,将其转为false;否则输出空格
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1010, M = 10010;
int n, m;
struct Edge
{
int a, b;
}e[M];
int p[N];
int main()
{
cin >> n >> m;
for (int i = 0; i < m; i ++ ) cin >> e[i].a >> e[i].b;
int k;
cin >> k;
bool is_first = true;
for (int i = 0; i < k; i ++ )
{
for (int j = 1; j <= n; j ++ )
{
int x;
cin >> x;
p[x] = j;
}
bool success = true;
for (int j = 0; j < m; j ++ )
if (p[e[j].a] > p[e[j].b])
{
success = false;
break;
}
if (!success)
{
if (is_first) is_first = false;
else cout << ' ';
cout << i;
}
}
cout << endl;
return 0;
}
1150 Travelling Salesman Problem (25 分)
思路 :
- 如果所给的路径中不连通,success为false,sum为-1,输出NA;如果第一个点和最后一个不是同一个点,没有遍历所有点;遍历完路径后,如果有点没有被路径包含,则success为false,输出不是一个访问了所有城市的回路;
- 如果路径点的数量等于n+1,说明是简单回路,否则就不是简单回路
- 然后动态维护最小距离和最小距离的组号
- 无向带权图,因此初始化距离为正无穷
- 用sum表示路径和,如果是-1,说明无法计算;success表示是否是否访问了所有城市
#include <iostream>
#include <cstring>
using namespace std;
const int N = 210, INF = 0x3f3f3f3f;
int n, m;
int d[N][N], vers[310];
bool st[N];
int main()
{
cin >> n >> m;
memset(d, 0x3f, sizeof d);
for (int i = 0; i < m; i ++ )
{
int a, b, c;
cin >> a >> b >> c;
d[a][b] = d[b][a] = c;
}
int k;
cin >> k;
int min_dist = INF, min_id;
for (int T = 1; T <= k; T ++ )
{
int cnt;
cin >> cnt;
for (int i = 0; i < cnt; i ++ ) cin >> vers[i];
int sum = 0;
bool success = true;
memset(st, 0, sizeof st);
for (int i = 0; i + 1 < cnt; i ++ )
{
int a = vers[i], b = vers[i + 1];
if (d[a][b] == INF)
{
sum = -1;
success = false;
break;
}
else sum += d[a][b];
st[a] = true;
}
for (int i = 1; i <= n; i ++ )
if (!st[i])
{
success = false;
break;
}
if (vers[0] != vers[cnt - 1]) success = false;
if (sum == -1) printf("Path %d: NA (Not a TS cycle)\n", T);
else
{
if (!success) printf("Path %d: %d (Not a TS cycle)\n", T, sum);
else
{
if (cnt == n + 1) printf("Path %d: %d (TS simple cycle)\n", T, sum);
else printf("Path %d: %d (TS cycle)\n", T, sum);
if (min_dist > sum)
{
min_dist = sum;
min_id = T;
}
}
}
}
printf("Shortest Dist(%d) = %d\n", min_id, min_dist);
return 0;
}
1154 Vertex Coloring (25 分)
题意 :
- 一个合适的顶点着色是指用各种颜色标记图中各个顶点,使得每条边的两个端点的颜色都不相同。
- 如果一种合适的顶点着色方案使用了一共 k 种不同的颜色,则称其为合适的 k 着色(k-coloring)。
- 现在,你需要判断给定的着色方案是否是合适的 k 着色方案。
思路 :
- 用结构体存边
- 对于每一个询问,首先遍历所有边,如果所有边的颜色都没有出现矛盾,再判断颜色数量是否是k种
#include <iostream>
#include <cstring>
#include <unordered_set>
using namespace std;
const int N = 10010;
int n, m;
struct Edge
{
int a, b;
}e[N];
int color[N];
int main()
{
cin >> n >> m;
for (int i = 0; i < m; i ++ ) cin >> e[i].a >> e[i].b;
int k;
cin >> k;
while (k -- )
{
for (int i = 0; i < n; i ++ ) cin >> color[i];
bool success = true;
for (int i = 0; i < m; i ++ )
if (color[e[i].a] == color[e[i].b])
{
success = false;
break;
}
if (success)
{
unordered_set<int> S;
for (int i = 0; i < n; i ++ ) S.insert(color[i]);
printf("%d-coloring\n", S.size());
}
else puts("No");
}
return 0;
}