我这个老拖延被罚了,这次补上这几道题
第一题
输入输出样例
输入 #1
7 6 1 3 2 3 3 4 3 5 4 5 5 6 1 6
输出 #1
2
第一个题,是讲解的例题,注意下图的存储就行了,这题是无向图,所以使用vector存储图的结构时要一次push两个节点。思路就是,用dfs遍历图,基本操作是存储路径信息和使用vis数组判断节点是否访问,然后因为这题需要记录下起点到终点中间每条路有几个节点,所以用一个数组来存储每个节点访问的次数,还要有变量记录总路径条数。
可以这么想,如果两个节点中间只有一条路径,那么这条路径上面的所有点都是关键节点,但是如果有两条路径,但是没有交织,那么就不存在关键节点了,如果有两条路径,且路径共同交于一个或者一些点,那么这些点也是关键节点(因为掐掉这个点就相当于掐掉两条路)。这么一想也就明白了,记录下dfs访问每个节点的次数,如果次数等于路径条数,那么就是掐掉这个点能掐掉这几条路径,就是关键节点了。会找关键节点,危险系数自然是轻轻松松简简单单了。不愧是蓝桥的赛题。
#include <iostream>
#include <vector>
#include <map>
using namespace std;
const int MAX = 2000;
//我总算他喵的明白了
//要记录总路径数和每个节点经过的次数,经过次数等于总路径数才是关键节点
vector<int> dirs[MAX+1]; //节点的路径
bool vis[MAX+1] = {0}; //节点是否被访问
int nums[MAX+1] = {0}; //每个节点的经过次数
// bool flag = false; //是否到达终点
int cnt = 0; //从起点到终点的总路径数
int b = 0, e = 0; //起始节点,结束节点
int n, m; //总节点数,总路径数
//遍历搜索的算法
void dfs(int beg, int en)
{
if(beg == en)
{
cnt++;
// flag = true;
//节点的下标从1开始,如果到达过就++
for(int i = 1; i <= n; i++)
{
if(i != b && i != e && vis[i])
{
nums[i]++;
}
}
}
//每个节点路径下标从0开始
for(int i = 0; i < dirs[beg].size(); i++)
{
int next = dirs[beg][i];
if(!vis[next])
{
vis[next] = true;
dfs(next, en);
vis[next] = false;
}
}
}
int main()
{
cin >> n >> m;
//输入输出部分的实现
//坑点:这个是双向图
for(int i = 0; i < m; i++)
{
int a, b;
cin >> a >> b;
dirs[a].push_back(b);
dirs[b].push_back(a);
}
cin >> b >> e;
vis[b] = true;
dfs(b, e);
int ans = 0;
if(!cnt)
{
cout << -1 << endl;
return 0;
}
for(int i = 1; i <= n; i++)
{
if(cnt == nums[i])
ans++;
}
cout << ans << endl;
return 0;
}
第二题
输入输出样例
输入 #1
4 3 1 2 2 4 4 3
输出 #1
4 4 3 4
说明/提示
- 对于 60% 的数据,10^3≤N,M≤10^3。
- 对于 100% 的数据,10^5≤N,M≤10^5。
那么看到这个题似乎觉得比上一个简单?但是想想看,图的节点数挺多的,如果采用暴力枚举的办法的话,算法时间复杂度至少是二次,会不会超时?事实是如此的
我们怎么办?反向存图。本题的图是单向图,我们存储原图的反向图,然后从较大的结点出发,去遍历图,能达到的较小的节点用一个int数组标记上该大节点的值,同样还得用vis数组,因为大节点标记过的点小结点不用管了。循环往复,直到遍历完为止,因为有重复返回的机制,所以会少走很多弯路。
#include <bits/stdc++.h>
using namespace std;
const int MAX = 100000;
int n, m;
vector<int> dirs[MAX+1]; //一个vector数组
int max_x[MAX+1] = {0}; //存放每个点最大的值
bool vis[MAX+1] = {0}; //存放每个点是否到达
//finish是反向遍历的终点,也就是真实的起点
void dfs(int start, int finish)
{
//如果start点已经被到达,那么就无需在访问
if(max_x[start])
return;
max_x[start] = finish;
if(dirs[start].empty())
return;
for(int i = 0; i < dirs[start].size(); i++)
{
int next = dirs[start][i];
if(!vis[next])
{
vis[next] = true;
dfs(next, finish);
vis[next] = false;
}
}
}
int main()
{
cin >> n >> m;
for(int i = 0; i < m; i++)
{
int a, b;
cin >> a >> b;
// dirs[a].push_back(b);
//这题是单向图,我们需要反向存图
dirs[b].push_back(a);
}
for(int i = n; i >= 0; i--)
{
vis[i] = true;
dfs(i, i);
vis[i] = false;
}
for(int i = 1; i <= n; i++)
printf("%d ", max_x[i]);
return 0;
}
第三题
输入输出样例
输入 #1
3 3 1 2 1 3 2 3
输出 #1
Impossible
输入 #2
3 2 1 2 2 3
输出 #2
1
说明/提示
【数据规模】
对于 100% 的数据,10^4≤n≤10^4, 10^5≤m≤10^5,保证没有重边。
这个题看到就被吓到了,真的没思路啊,但是想想,我们在刷视频是或许会遇到类似的题,甚至是无解的题(用哈二叔的话,这就是个无尽)那么和这个题最类似的题是什么呢?就是填染料了,要相邻节点的颜色(状态)互不相同(就是一个点有螃蟹,另一个点就不能有)。
那么我们就可以用涂颜色的方式来做这题。我们一共有两种颜色,1和-1(0是没有染色)我们要用bfs来循环遍历图(因为bfs更加直观一些当然也可以dfs),当一个之前已经访问的节点和现在相邻的节点同色,那么就是任务失败。我们要找到两种颜色的最小总数目(用变量记录两种颜色节点的总数)因为图可能是不连通的,所以每一个节点都要bfs一次且,每次都要加上这两种颜色的最小总数目。(可以这么理解,不连通的图可以分割为连通的子图,每个子图的最小值相加)
代码:
#include <bits/stdc++.h>
using namespace std;
const int MAX = 10002;
int n, m;
vector<int> nodes[MAX];
int color[MAX] = {0}; //这个节点的颜色
bool vis[MAX] = {0}; //是否到过这个节点
int main()
{
cin >> n >> m;
for(int i = 0; i < m; i++)
{
int u, v;
cin >> u >> v;
nodes[u].push_back(v);
nodes[v].push_back(u);
}
//节点数目
queue<int> dui; //结点的队列
int ans = 0; //结果的值
for(int i = 1; i <= n; i++)
{
int a = 0, b = 0;
if(!vis[i])
{
dui.push(i); //节点入队
vis[i] = true;
color[i] = 1; //默认的是第一种颜色
a++;
}
while(!dui.empty())
{
int now = dui.front();
dui.pop(); //访问接下来的节点,就相当于一个bfs
for(int i = 0; i < nodes[now].size(); i++)
{
int next = nodes[now][i];
if(!vis[next]) //如果没有访问这个节点
{
dui.push(next);
vis[next] = true;
color[next] = -color[now];
if(color[next] == 1)
a++;
else
b++;
}
else
{
if(color[next] == color[now])
{
cout << "Impossible" << endl;
return 0;
}
}
}
}
ans += min(a, b);
}
// cout << min(a, b) << endl;//这个是不对的
//图可能是不连通的,所以每条支路都要加上最小值
cout << ans << endl;
return 0;
}