Contest - DAY07
T1 - 3160 打扫 k
具体分析
考点:图的深搜
;
题意:给定 n n n 个点, m m m 条无向边, 1 1 1 号点作为起点,找到达 k k k 点的路径数量。
根据 样例 可以建模如下图所示:
- 不难发现共计有 4 4 4 条符合题目要求的路径。
1-2-5-7-3-8
;1-2-6-7-3-8
;1-6-7-3-8
;1-6-2-5-7-3-8
;
本题步骤 & 注意点:
- 从点 u = 1 u=1 u=1 开始出发,通过深度优先搜索的方式"能深则深"的方式前进,如果当前搜索的点 u u u 到达了 k k k 点,那么统计的数量加一。
- 遍历 u u u 的所有邻点 v v v,没有被标记过就深搜该点 v v v,同时打上标记
vis[v] = true;
代表 u u u 点已经访问"避免回走"。 - 当点 u u u 抵达点 k k k,将访问的路径数量增加。
- 注意标记要回溯
vis[v] = false;
尝试其他的方向是否到达点 k k k。
参考代码
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 15;
int n, m,k,ans;
vector<int> e[N]; // 不确定边的数量,建议使用邻接表,动态分配点对应的边的数量
bool vis[N]; // 避免回走,使用标记数组,表示该点是否已经被走访
void add( int u, int v ) {
e[u].push_back( v );
}
void dfs( int u ) {
if ( u == k ) {
ans++;
return;
}
// vis[u] = true; // 先标记当前点已经走访
for ( auto v : e[u] ) {
if ( !vis[v] ) {
vis[v] = true;
dfs( v );
vis[v] = false;
}
}
}
int main() {
cin >> n >> m;
for (int i = 1, u, v; i <= m; i++)
{
cin >> u >> v;
add( u, v ), add( v, u ); // 无向图双向建边
}
cin >> k;
// 统计路径数量,dfs
vis[ 1 ] = true;
dfs( 1 );
cout << ans << endl;
return 0;
}
T2- 11961 逃离星系1
具体分析
考点:图的广搜/深搜
;
题意:给出 n n n 个点 m m m 条边的无向图,从 1 1 1 号点出发到达指定的 h h h 号点,从一个结点到达另外一个结点需要 1 min
,到达该点后如果**还需要再出发则需要停留 1 min
**才可以再出发。
限定条件:
- 1 1 1 号点的时间为 0 0 0,到达 k k k 号点如果 ≤ 10 \le 10 ≤10 min,则输出
YES
,否则输出NO
;
如果有需要观察样例 1 1 1 的模型,可以看下图:
参考代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 55;
int n, m, h;
bool flag = false;
bool vis[N]; // 标记数组,防止点回走
vector<int> e[N];
struct node
{
int id, step;
};
queue<node> q;
void add(int u, int v) // 加边
{
e[u].push_back(v);
}
void bfs(int start, int step)
{
// 起点入队,打上标记
q.push((node){start, step});
while (q.size())
{
node top = q.front();
q.pop();
vis[top.id] = true; // 给父节点打上标记,防止回走
for (auto v : e[top.id])
{
if (!vis[v])
{
if (v == h && top.step + 1 <= 10) // 找到答案
{
flag = true;
return;
}
q.push((node){v, top.step + 2}); // 符合结点入队
}
}
}
}
int main()
{
cin >> n >> m;
for (int i = 1, u, v; i <= m; i++)
{
cin >> u >> v;
add(u, v); // 无向图双向建边
add(v, u);
}
cin >> h;
bfs(1, 0);
if (flag) // 存在答案
cout << "YES" << endl;
else
cout << "NO" << endl;
system("pause");
return 0;
}
T3 - 2615 种草
- P5197 [USACO19JAN] Grass Planting S;
具体分析
参考代码
做法一:树形 DP;
分析:
- 设 f [ x ] f[x] f[x] 表示 x x x 的子树需要草的种类数。
那么,我们很容易得到状态转移方程
f [ x ] = m a x ( f [ v ] , s o n [ v ] + 2 , s o n [ x ] + 1 ) ; f[x]=max(f[v],son[v]+2,son[x]+1); f[x]=max(f[v],son[v]+2,son[x]+1);
其中 v v v 为 x x x 的子节点, s o n [ x ] son[x] son[x] 表示 x x x 的子节点的个数, s o n [ v ] son[v] son[v] 表示 v v v 的子节点的个数
因为要让子树中的每个节点接近相邻和相邻节点类别不同,
所以我们要求子树中最大的一个每个节点都与其他节点接近相邻或相邻的集合。
son[v] + 2
是 x x x 节点与 x x x 的一个子节点以及这个子节点的所有子节点, son[x]+1
是 x x x 与 x x x 的所有子节点, f [ v ] f[v] f[v] 是 x x x 的一个子节点的子树中的不同种类数, 三者取最大就是 f [ x ] f[x] f[x] 的值。
- O ( n ) O(n) O(n);
cpp
#include <bits/stdc++.h>
using namespace std;
// 做法 1:树形 DP
const int N = 5e5 + 1000;
int n;
int son[N], f[N];
vector<int> e[N];
void add(int u, int v)
{
e[u].push_back(v);
}
void dfs(int u, int fa)
{
son[u] = e[u].size() - 1; // 计算子节点数量
if (u == 1)
son[u]++;
for (auto v : e[u])
{
if (v == fa) // 防止回走
continue;
dfs(v, u); // 深度搜索
f[u] = max({f[u], f[v], son[v] + 2}); // 从儿子中转移,子树数量 >=2
}
f[u] = max(f[u], son[u] + 1); // 从当前结点上转移,子树数量 <2
}
int main()
{
cin >> n;
for (int i = 1, u, v; i < n; i++)
{
cin >> u >> v;
add(u, v), add(v, u);
}
dfs(1, 0);
cout << f[1] << endl;
system("pause");
return 0;
}
做法二:观察规律、推导总结
不可行的方案:
那么一个有效的添草方式是什么呢?这边引入一种图,菊花图:
灰圈代表可以连接更多草的点,那么想一下假设我们最多就用 这 7 种草,灰圈内可以最多连接多少个草?
答案是五个,拿 G 举例:
你发现了什么?
若最大子树的大小是 x x x(代表用了 x x x 种草),那么任何其他的子树也最多是 x x x
答案最后要加 1 1 1,因为子树包含它自己。
- O ( n ) O(n) O(n);
cpp
#include <bits/stdc++.h>
using namespace std;
// 做法二:观察树的性质,类似与菊花图。
/* 求以每个点作为中心,可以展开的儿子结点数量 child,找 child 值最大,max{child}+1即为答案 */
const int N = 1e5 + 10;
int ind[N];
int n, ans;
int main()
{
cin >> n;
for (int i = 1, u, v; i < n; i++)
{
cin >> u >> v;
ind[u]++, ind[v]++;
ans = max({ans, ind[u], ind[v]});
}
cout << ans + 1 << endl;
system("pause");
return 0;
}
T4 - 8917 路线
具体分析
考点:图的搜索、反向建图 / 反向跑图技巧
;
样例模型:
题意:给定 n n n 个点, m m m 条边,无向图。
如果正常跑图,每一个点都有可能作为起点进行 dfs(i,0)
,找可以到达 n n n 点最短路。最坏的情况是一条链的情况,当 n n n 的点数较大时,如 n = 1 0 5 n=10^5 n=105 时,最坏的情况是 n 2 = 1 0 10 n^2=10^{10} n2=1010,超过 1 s 1s 1s 的运算量。但本题数据量比较小,思路已经给到了这种方法的代码不展示。
有没有一种方法,可以将问题简单化?
找 1 ∼ n − 1 1 \sim n-1 1∼n−1 个点作为起点到达 n n n 的最短路,转化为以 n n n 点为起点到达 1 ∼ n − 1 1 \sim n-1 1∼n−1 个点的最短路,就非常简单了,直接 BFS 以 n n n 作为起点,到达每个点的最短路径。
参考代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 110, inf = 0x3f3f3f3f;
int n, m, f[N];
vector<int> e[N];
struct node
{
int u, step;
};
queue<node> q;
bool vis[N]; // 标记数组,防止回走
void add(int u, int v)
{
e[u].push_back(v);
}
void bfs(int u, int step)
{
// 起点入队
q.push((node){u, step});
vis[u] = true;
while (q.size())
{
auto top = q.front();
q.pop();
for (auto v : e[top.u])
{
if (!vis[v]) // 防止回走
{
vis[v] = true;
f[v] = min(f[v], top.step + 1); // 求最少边到达 v 点
q.push((node){v, top.step + 1});
}
}
}
}
int main()
{
cin >> n >> m;
// memset(f, 0x3f, sizeof f);
for (int i = 1; i <= n; i++) // 求最少步数,初始化最大
f[i] = inf;
f[n] = 0;
for (int i = 1, u, v; i <= m; i++)
{
cin >> u >> v;
add(u, v);
add(v, u);
}
bfs(n, 0); // 反向跑 bfs
for (int i = 1; i < n; i++)
if (f[i] != inf)
cout << f[i] << " ";
else
cout << -1 << " ";
system("pause");
return 0;
}
T5 - 4471 网的邻接矩阵2
具体分析
考点:邻接矩阵、建图、初始化
;
- 初始化图中权值为 999;
- 读入边的信息,无向图双向建边;
- 输出地图的信息;
参考代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 25;
int f[N][N];
int n, m;
int main()
{
cin >> n >> m;
// 初始化图
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
f[i][j] = 999;
for (int i = 1, u, v, w; i <= m; i++)
{
cin >> u >> v >> w;
f[u][v] = w, f[v][u] = w;
}
for (int i = 1; i <= n; i++, cout << endl)
for (int j = 1; j <= n; j++)
cout << f[i][j] << " ";
system("pause");
return 0;
}
T6 - 2575 蹄球
- P1677 [USACO18FEB] Hoofball B;
分析
-
考点:
思维题、基环树、topo排序
; -
题意:给一个数轴,有 n n n 个奶牛所在位置 x i x_i xi,第 i i i 个奶牛传球的时候只会传给最近的奶牛 i − 1 i-1 i−1 或 i + 1 i+1 i+1, m i n ∣ x i − x i − 1 , x i − x i + 1 ∣ min| x_{i}-x_{i-1},x_i-x_{i+1} | min∣xi−xi−1,xi−xi+1∣,每个奶牛都至少持球一次。
-
问题:问满足条件下,要传出球的次数最少为多少?
样例 & 解读:
cpp
样例输入
5 // 牛奶数量
7 1 3 11 4 // x 轴上位置
样例输出
2 // 至少穿出球 2 次才可以使得所有奶牛都持球一次
传球第 1 1 1 次:
1->3->4 [->3->4];
传球第 2 2 2 次:
11->7->4->3[->4->3];
图文分析:
参考代码
cpp
#include <bits/stdc++.h>
using namespace std;
/* 结论题 */
const int N = 110, inf = 1e9;
// 方便处理,将左边和右边边界,置于正无穷和负无穷。
int n;
int q[N], tag[N], ind[N];
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
cin >> q[i];
q[0] = -inf, q[n + 1] = inf; // 边界点,初始化最值
sort(q + 1, q + 1 + n);
// 枚举并记录每个点的走向
for (int i = 1; i <= n; i++)
if (q[i] - q[i - 1] <= q[i + 1] - q[i]) // turn left
{
tag[i] = i - 1; // 左边点
ind[i - 1]++; // 记录入度
}
else
{
tag[i] = i + 1;
ind[i + 1]++;
}
int res = 0;
for (int i = 1; i <= n; i++) // 枚举每个点的贡献
{
if (!ind[i])
{
res += 2;
}
/* 环内的点条件:
1. 点 i 多走一遍到走回点 i
2. 环内的点 i 与另外一个点 tag[i] 入度都必须等于 1
*/
else if (ind[i] == 1 && ind[tag[i]] == 1 && tag[tag[i]] == i)
{
res++;
}
}
cout << res / 2 << endl;
system("pause");
return 0;
}