蓝桥杯练习笔记(十一)
一、
- 题解:
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
int ans = 50, cnt = 0;
ll _sum = 0;
ll n, m, a[35], sum[35];
void dfs(ll _sum, int x, int cnt)
{
if (cnt >= ans)return;
if (_sum == m)ans = min(ans, cnt);//注意这一行和下一行之间是有顺序要求的
if (x >= n || _sum >= m || _sum + sum[x] < m)return;
dfs(_sum + a[x], x + 1, cnt);
dfs(_sum + a[x] / 2, x + 1, cnt + 1);
dfs(_sum, x + 1, cnt);
}
int main()
{
// 请在此输入您的代码
cin >> n >> m;
m <<= 1;
for (ll i = 0;i < n;i++)cin >> a[i], a[i] <<= 1;
sort(a, a + n, greater<ll>());
for (int i = n - 1;i >= 0;i--)sum[i] = sum[i + 1] + a[i];
dfs(0, 0, 0);
if (ans == 50)cout << -1;
else cout << ans << endl;
return 0;
}
这个题用DFS进行搜索即可,然后就是数据的预处理和跳出条件注意一下。
预处理,这里先将原数据进行了排序,然后用了一个数组来维护后缀和以便后边跳出条件的使用。
跳出条件:注意上面打注释的地方,那两行之间的顺序不能调换,因为这里的DFS对各个数据的处理都在if和调用DFS时完成。尤其是那两个if之间,条件有重合部分,若调换顺序则会导致赋值无法进行,最后导致结果出错。
二、最小生成树
样例输入:
5 4 3
1 2 5
2 3 6
3 4 1
4 5 10
1 4
3 4
1 3
- 官网基于kruskal的题解:
#include <bits/stdc++.h>
using namespace std;
const int inf = 1e6+5;
const int N = 1e5+1;
const int M = 3e5+1;
const int K = 17;
struct Edge{
int u,v,w;
bool operator<(const Edge& e)const{
return w > e.w;
}
};
int n,m,q;
Edge edge[M];
vector<pair<int,int>> g[N];
bool visit[N]; //记录节点是否被访问
int p[N+1]; //记录节点的祖宗节点
//lca的辅助数组,分别为祖先数组,代价数组,深度数组
int fa[N+1][K+1],cost[N+1][K+1],dep[N];
//查找x的祖先
int find(int x){
return x==p[x]?x:p[x] = find(p[x]);
}
//使用kruskal算法获取图的最大生成树
void kruskal(){
//将边按权值降序排序
sort(edge,edge+m);
//创建生成树
for(int i=0;i<m;i++){
auto [u,v,w] = edge[i];
int pu = find(u),pv = find(v);
if(pu != pv) {
p[pv] = pu;
g[u].push_back({v,w});
g[v].push_back({u,w});
}
}
}
//dfs遍历生成树,为lca算法做准备
void dfs(int root,int p){
visit[root] = true;
fa[root][0] = p;
dep[root] = dep[p] + 1;
//利用倍增求fa和cost: 第2^j个祖先节点是第2^(j-1)个祖先节点的2^(j-1)个祖先节点
for(int j=1;j<=K;j++){
if(fa[root][j-1] > 0){
fa[root][j] = fa[fa[root][j-1]][j-1];
cost[root][j] = min(cost[root][j-1],cost[fa[root][j-1]][j-1]);
}
}
//遍历子节点
for(auto [y,w]:g[root]){
if(y != p){
cost[y][0] = w;
dfs(y,root);
}
}
}
//lca算法求两个节点间路径上的最小权值
int lca(int x,int y){
//使x和y的深度相等
if(dep[x] > dep[y]) swap(x,y);
int tmp = dep[y] - dep[x];
int ans = inf;
for(int j=0;tmp>0;j++,tmp>>=1){
if(tmp&1) {
ans = min(ans,cost[y][j]);
y = fa[y][j];
}
}
//到达同一节点,直接返回
if(x == y) return ans;
//找到第一个不是x,y祖先节点的节点
for(int j=K;j>=0;j--){
if(fa[x][j] != fa[y][j]){
ans = min(ans,min(cost[x][j],cost[y][j]));
x = fa[x][j],y = fa[y][j];
}
}
//此时x,y的父节点为最近公共祖先
ans = min(ans,min(cost[x][0],cost[y][0]));
return ans;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m>>q;
memset(visit,0,sizeof(visit));
memset(dep,0,sizeof(dep));
memset(cost,0,sizeof(cost));
memset(fa,0,sizeof(fa));
//初始化p数组
for(int i=1;i<=n;i++) p[i] = i;
for(int i=0;i<m;i++){
int u,v,w;
cin>>u>>v>>w;
edge[i] = {u,v,w};
}
kruskal();
//遍历所有的连通分支
for(int i=1;i<=n;i++){
if(!visit[i]) dfs(i,0);
}
while(q--){
int x,y;
cin>>x>>y;
if(find(x) != find(y)) cout<<"-1\n"; //如果不连通,直接返回-1
else cout<<lca(x,y)<<"\n";
}
return 0;
}
kruskal算法是将图转化为树的一种算法,适用于稀疏图,中间的核心部分是并查集算法。
并查集主要包含以下两个操作:
Find操作(查找):用于找到某个节点所在集合的根节点,通常通过递归或循环向上查找直到找到根节点。
Union操作(合并):用于合并两个集合,即将两个集合的根节点连接起来,通常是将一个集合的根节点指向另一个集合的根节点。
通过这种数据结构和操作,可以高效地处理集合的合并与查找操作,常用于解决诸如连通性、最小生成树等问题。在代码中,通过并查集实现了Kruskal算法来构建无向图的最大生成树。
Find操作对应着:
//查找x的祖先
int find(int x){
return x==p[x]?x:p[x] = find(p[x]);
}
Union操作对应着这里的if内容,也就是把根节点换成另一个根节点,最终只剩下一个根节点(如果最后是森林的话另说)
//使用kruskal算法获取图的最大生成树
void kruskal(){
//将边按权值降序排序
sort(edge,edge+m);
//创建生成树
for(int i=0;i<m;i++){
auto [u,v,w] = edge[i];
int pu = find(u),pv = find(v);
if(pu != pv) {
p[pv] = pu;
g[u].push_back({v,w});
g[v].push_back({u,w});
}
}
}
这段代码还有个很优秀的处理就是这其实如果最后得到是森林也能处理,因为这段代码保证了如果每个节点都能连通的话只需要一遍DFS就行了,换句话说DFS了几遍就有几棵树。
//遍历所有的连通分支
for(int i=1;i<=n;i++){
if(!visit[i]) dfs(i,0);
}