蓝桥杯2023真题(4)

文章讨论了如何通过优化dfs算法和数据结构来解决与景区导游路线、树上前缀和、差分以及贪心策略相关的问题,包括最近公共祖先的计算,以及如何将边权转化为点权以提高效率。
摘要由CSDN通过智能技术生成

1.景区导游(树上前缀和、最近公共祖先)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

思路

路线:2 -> 6 -> 5 -> 1
1.一个点都不去去掉的花费记作sum
2.去掉第一个点,sum - cost[2 -> 6]
3.去掉第二个点,sum - cost[2 -> 6] - cost[6 -> 5] + cost[2 -> 5]

dfs 中的参数不是一下就想到的,而是在写的过程中,你发现需要某个信息,而这个信息没有被提前记录,所以可以把这个信息当作参数记录下来

正解就是优化 cost 的求法

暴力 dfs(O(k * n))

#include<iostream>
#include<vector>
#include<map>
using namespace std;
const int N = 2e5 + 10;
map<pair<int, int>, int> st; // 存储两个点之间的时间
vector<pair<int, int>> g[N]; // 存储图和时间
int n, k;
int a[N];

// 起点、终点、当前节点、父节点、时间总和
bool dfs(int u, int v, int s, int fa, int sum){
    if(s == v){
        st[make_pair(u, v)] = sum;
        st[make_pair(v, u)] = sum;
        return true;
    }
    for(int i = 0; i < g[s].size(); i++){
        // 子节点
        int son = g[s][i].first, w = g[s][i].second;
        // 如果子节点等于父节点,说明重复了跳过
        if(son == fa) continue;
        // 父节点为当前节点
        if(dfs(u, v, son, s, sum + w)) return true;
    }
    return false;
}

int main(){
    scanf("%d%d", &n, &k);
    int u, v, t;
    for(int i = 0; i < n - 1; i++){
        scanf("%d%d%d", &u, &v, &t);
        g[u].push_back(make_pair(v, t));
        g[v].push_back(make_pair(u, t));
    }
    for(int i = 0; i < k; i++) scanf("%d", &a[i]);
    int ans = 0;
    for(int i = 1; i < k; i++){
        dfs(a[i - 1], a[i], a[i - 1], -1, 0);
        ans += st[make_pair(a[i - 1], a[i])];
    }
    for(int i = 0; i < k; i++){
        int res = ans;
        if(!i) res -= st[make_pair(a[i], a[i + 1])];
        else if(i == k - 1) res -= st[make_pair(a[i - 1], a[i])];
        else{
            res -= st[make_pair(a[i - 1], a[i])];
            res -= st[make_pair(a[i], a[i + 1])];
            dfs(a[i - 1], a[i + 1], a[i - 1], -1, 0);
            res += st[make_pair(a[i - 1], a[i + 1])];
        }
        printf("%d ", res);
    }
    return 0;
}

正解(O(n))

在这里插入图片描述

#include<iostream>
#include<vector>
using namespace std;
const int N = 2e5 + 10;
typedef long long qq;
vector<pair<int, int>> e[N];
// 存储父节点,存储深度,存储重儿子,存储以当前节点为根的子树节点数,存储当前节点的重链顶点
int fa[N], dep[N], son[N], sz[N], top[N]; 
qq sum[N]; // 存储树上前缀和
int a[N];
int n, k;

// 求出 fa, dep, son, sz
void dfs1(int u, int father){
    fa[u] = father, dep[u] = dep[father] + 1, sz[u] = 1;
    for(auto v : e[u]){
        int s = v.first;
        if(s == father) continue;
        dfs1(s, u);
        sz[u] += sz[s];
        if(sz[son[u]] < sz[s]) son[u] = s;
    }
}

// 求出 top
void dfs2(int u, int t){
    // 链头
    top[u] = t;
    // 没有重儿子
    if(!son[u]) return;
    // 搜重儿子
    dfs2(son[u], t);
    for(auto v : e[u]){
        int s = v.first;
        if(s == fa[u] || s == son[u]) continue;
        // 搜轻儿子
        dfs2(s, s);
    }
}

// 求公共祖先
int lca(int u, int v){
    while(top[u] != top[v]){
        if(dep[top[u]] < dep[top[v]]) swap(u, v);
        u = fa[top[u]];
    }
    return dep[u] < dep[v] ? u : v;
}

// 求树上前缀和
void cal_sum(int u){
    for(auto v : e[u]){
        int s = v.first, w = v.second;
        if(s == fa[u]) continue;
        sum[s] = sum[u] + w;
        cal_sum(s);
    }
}

int main(){
    scanf("%d%d", &n, &k);
    int u, v, t;
    for(int i = 1; i < n; i++){
        scanf("%d%d%d", &u, &v, &t);
        e[u].push_back({v, t});
        e[v].push_back({u, t});
    }
    for(int i = 1; i <= k; i++) scanf("%d", &a[i]);
    
    dfs1(1, 0);
    dfs2(1, 1);
    
    cal_sum(1);
    
    qq ans = 0;
    for(int i = 2; i <= k; i++){
        int x = a[i - 1], y = a[i];
        qq cost = sum[x] + sum[y] - 2 * sum[lca(x, y)];
        ans += cost;
    }
    
    for(int i = 1; i <= k; i++){
        qq res = ans;
        int x = a[i - 1], y = a[i], z = a[i + 1];
        if(i == 1) res -= sum[y] + sum[z] - 2 * sum[lca(y, z)];
        else if(i == k) res -= sum[x] + sum[y] - 2 * sum[lca(x, y)];
        else{
            res -= sum[x] + sum[y] - 2 * sum[lca(x, y)];
            res -= sum[y] + sum[z] - 2 * sum[lca(y, z)];
            res += sum[x] + sum[z] - 2 * sum[lca(x, z)];
        }
        printf("%lld ", res);
    }
    return 0;
}

2.砍树(树上差分、最近公共祖先)

在这里插入图片描述
在这里插入图片描述

思路

dfs 找到两个点之间的路径,然后打上标记,如果打上 m 个标记,那么就可以作为答案

正解:优化打标记的过程,边权转换为点权,每个边权为子节点的点权
第一步:给路径的起点和终点都加 1
第二步:给两个点的最近公共祖先减 2
第三步:树上差分
第四步:让每个点都加上他的子树和
点权等于这个点和他父节点相连的边权

暴力 dfs(O(n * m))

#include<iostream>
#include<vector>
#include<map>
using namespace std;
const int N = 2e5 + 10;
map<pair<int, int>, int> id; // 存储编号
vector<int> e[N];
int w[N]; // 存储边权
int n, m;

bool dfs(int a, int b, int u, int fa){
  if(u == b) return true;
  for(int v : e[u]){
    if(v == fa) continue;
    if(dfs(a, b, v, u)){
        int t = id[{u, v}];
        w[t]++;
        return true;
    }
  }
  return false;
}

int main()
{
  scanf("%d%d", &n, &m);
  int x, y;
  for(int i = 1; i < n; i++){
    scanf("%d%d", &x, &y);
    e[x].push_back(y);
    e[y].push_back(x);
    id[{x, y}] = i;
    id[{y, x}] = i;
  }
  int a, b;
  for(int i = 0; i < m; i++){
    scanf("%d%d", &a, &b);
    dfs(a, b, a, -1);
  }
  int ans = -1;
  for(int i = n - 1; i >= 1; i--){
      if(w[i] == m){
          ans = i;
          break;
      }
  }
  printf("%d", ans);
  return 0;
}

正解(O(n))

#include<iostream>
#include<vector>
#include<map>
using namespace std;
const int N = 2e5 + 10;
int fa[N], dep[N], son[N], sz[N], top[N];
map<pair<int, int>, int> id; // 存储编号
vector<int> e[N];
int w[N]; // 存储点权
int n, m;

void dfs1(int u, int father){
    fa[u] = father, dep[u] = dep[father] + 1, sz[u] = 1;
    for(int v : e[u]){
        if(v == father) continue;
        dfs1(v, u);
        sz[u] += sz[v];
        if(sz[son[u]] < sz[v]) son[u] = v;
    }
}

void dfs2(int u, int t){
    top[u] = t;
    if(!son[u]) return;
    dfs2(son[u], t);
    for(int v : e[u]){
        if(v == fa[u] || v == son[u]) continue;
        dfs2(v, v);
    }
}

int lca(int u, int v){
    while(top[u] != top[v]){
        if(dep[top[u]] < dep[top[v]]) swap(u, v);
        u = fa[top[u]];
    }
    return dep[u] < dep[v] ? u : v;
}

void lca_sum(int u, int father){
    for(int v : e[u]){
        if(v == father) continue;
        lca_sum(v, u);
        w[u] += w[v];
    }
}

int main()
{
  scanf("%d%d", &n, &m);
  int x, y;
  for(int i = 1; i < n; i++){
    scanf("%d%d", &x, &y);
    e[x].push_back(y);
    e[y].push_back(x);
    id[{x, y}] = i;
    id[{y, x}] = i;
  }
  
  dfs1(1, 0);
  dfs2(1, 1);
  
  int a, b;
  for(int i = 0; i < m; i++){
    scanf("%d%d", &a, &b);
    // 树上差分
    w[a]++, w[b]++;
    w[lca(a, b)] -= 2;
  }
  
  lca_sum(1, 0);
  
  int ans = -1;
  for(int i = 1; i <= n; i++){
      if(w[i] == m){
          int t = id[{i, fa[i]}];
          ans = max(ans, t);
      }
  }
  printf("%d", ans);
  return 0;
}

3.三国游戏(贪心)

在这里插入图片描述
在这里插入图片描述

思路

因为事件是独立的,所以可以从大到小排序,看 a - b - c 的和最多有多少个满足大于0

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N], c[N], d[N];
int n;

int f(int a[], int b[], int c[]){
  for(int i = 1; i <= n; i++) d[i] = a[i] - b[i] - c[i];
  sort(d + 1, d + n + 1, greater<int>());
  long long sum = 0;
  for(int i = 1; i <= n; i++){
    sum += d[i];
    if(sum <= 0){
      sum = i - 1;
      break;
    }
  }
  return sum > 0 ? sum : -1;
}

int main()
{
  scanf("%d", &n);
  for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
  for(int i = 1; i <= n; i++) scanf("%d", &b[i]);
  for(int i = 1; i <= n; i++) scanf("%d", &c[i]);
  int res = max(f(a, b, c), max(f(b, a, c), f(c, a, b)));
  printf("%d", res);
  return 0;
}

4.填充(贪心)

在这里插入图片描述

思路

在这里插入图片描述

#include <iostream>
using namespace std;
int main()
{
  string s;
  cin>>s;
  int res = 0;
  for(int i = 1; i < s.size(); i++){
    if(s[i - 1] == s[i] || s[i - 1] == '?' || s[i] == '?'){
      res++;
      // 成对匹配,如果匹配成功,就跳过后面那个,到后面的后面,再匹配
      i++;
    }
  }
  cout<<res;
  return 0;
}

5.翻转(模拟)

在这里插入图片描述
在这里插入图片描述

思路

如果两边和当前位置相反,且和 t 不同,那就翻转,最后再比较 s 和 t 是否相同

#include <iostream>
using namespace std;
int main()
{
  int d;
  cin>>d;
  string t, s;
  while(d--){
    cin>>t>>s;
    int res = 0;
    for(int i = 1; i < s.size() - 1; i++){
      if(s[i] != t[i] && s[i - 1] != s[i] && s[i] != s[i + 1]){
        s[i] ^= 1;
        res++;
      }
    }
    if(s == t) cout<<res<<endl;
    else cout<<-1<<endl;
  }
  return 0;
}
  • 13
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值