牛客暑期多校解题报告: 3

A

给出两棵树和对应节点的权值,给出k个点,从k个点中删除一个点,若在a树中剩余k-1个点的lca对应的权值大于在b树中剩余k-1个点lca对应的权值,则该种删除方法是有效的删除方法;要求求出有效方案数的总和。
考虑k-1个点的lca该如何维护,这里使用两个数组来维护一棵树k-1个点的lca:一个pre数组,pre[i]代表k个节点中前i个节点的lca;一个lst数组,lst[i]代表从节点i到节点k的lca。则删除点i后的lca即为getlca(pre[i-1], lst[i+1])。然后枚举删除的点,找出合法的方案即可。
代码里面求解lca使用的是dfs序+RMQ求解的,不明白的可以在网上找找相关资料。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>

using namespace std;
const int N = 2e5+5;
struct lcatree
{
    int pos[N], seq[N * 2], dep[N], val[N], f[N][20], tot = 0;
    bool vis[N];
    vector<int> son[N];
    void dfs(int u, int d) { // u表示当前结点,d表示深度
        vis[u] = 1;
        pos[u] = ++tot;  // 记录每个节点最后一次出现的位置
        seq[tot] = u;  // 记录欧拉序
        dep[tot] = d;  // 记录当前位置节点的深度
        for (auto s : son[u]) {  // 遍历所有子节点
            if (vis[s]) continue;
            dfs(s, d + 1);  // 在子树上深搜
            seq[++tot] = u;  // 记录回溯到的节点
            dep[tot] = d;  // 记录节点的深度
        }
    }
    void st_create() {  // 创建ST表,求解RMQ问题
                        // f[i][j] 记录欧拉序中从 i 到  i + 2^j - 1  区间内深度最低的点
        for (int i = 1; i <= tot; i++)
            f[i][0] = i;
        int k = log2(tot), f1, f2;
        for (int j = 1; j <= k; j++) {
            for (int i = 1; i <= tot - (1 << j) + 1; i++) {
                f1 = f[i][j - 1], f2 = f[i + (1 << (j - 1))][j - 1];
                f[i][j] = dep[f1] < dep[f2] ? f1 : f2;
            }
        }
    }
    int get_lca(int u, int v) {  // 两个节点的lca为两个节点最后出现位置的区间中深度最小的节点
        int l = pos[u], r = pos[v];  // 找到两个节点最后出现的位置
        if (l > r) swap(l, r);
        int k = log2(r - l + 1);
        int f1 = f[l][k], f2 = f[r - (1 << k) + 1][k];
        return dep[f1] < dep[f2] ? seq[f1] : seq[f2];  // 找到深度最小的节点
    }
    void init(int n) {
        memset(vis, 0, sizeof vis);
        int fa;
        for (int i = 1; i <= n; i++)
            scanf("%d", &val[i]);
        for (int i = 2; i <= n; i++) {
            scanf("%d", &fa);
            son[fa].push_back(i);
        }
        dfs(1, 0);
        st_create();
    }
} treea, treeb;
int key[N], prea[N], preb[N], lsta[N], lstb[N];

int main(void)
{
    int n, k, ans = 0;
    scanf("%d%d", &n, &k);
    for(int i = 1; i <= k; ++i) {
        scanf("%d", &key[i]);
    }
    treea.init(n);
    treeb.init(n);
    prea[1] = key[1];
    preb[1] = key[1];
    for(int i = 2; i <= k; ++i) {
        prea[i] = treea.get_lca(prea[i-1], key[i]);
        preb[i] = treeb.get_lca(preb[i-1], key[i]);
    }

    lsta[k] = key[k];
    lstb[k] = key[k];

    for(int i = k-1; i >= 1; --i) {
        lsta[i] = treea.get_lca(lsta[i+1], key[i]);
        lstb[i] = treeb.get_lca(lstb[i+1], key[i]);
    }

    for(int i = 1; i <= k; ++i) {
        if(i == 1) {
            if(treea.val[lsta[2]] > treeb.val[lstb[2]])
                ++ans;
        } else if(i == k) {
            if(treea.val[prea[k-1]] > treeb.val[preb[k-1]])
                ++ans;
        } else {
            int lcaa = treea.get_lca(prea[i-1], lsta[i+1]);
            int lcab = treeb.get_lca(preb[i-1], lstb[i+1]);
            if(treea.val[lcaa] > treeb.val[lcab])
                ++ans;
        }
    }
    printf("%d\n", ans);
    return 0;
}

J

题目给出了一张地图,地图中有n个十字路口。每一行按照逆时针方向给出当前路口连接的点。在每个十字路口,右转到达的路不需要等红绿灯,其他的方向均需要等一个红绿灯。给出起始所在的道路和终点所在的道路,要求求出最短经过的红绿灯的数目。
本质上就是一个最短路的问题,右转的消耗为0,其他方向为1;同时这个问题还是01bfs,可以使用双端队列加速问题的求解。这个题目的意思比较难理解,问题的求解还是比较简单的,后悔当时场上没有写😭。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <deque>

using namespace std;
typedef pair<int, int> PII;
const int maxn = 5e5+5;
int mp[maxn][4];
bool vis[maxn][4];

int s1, s2, t1, t2;
deque<pair<PII, int> > que;

int main(void)
{
    /* freopen("in.txt", "r", stdin); */
    int n;
    scanf("%d", &n);
    for(int i = 1 ; i <= n; ++i) {
        scanf("%d%d%d%d", &mp[i][0], &mp[i][1], &mp[i][2], &mp[i][3]);
        vis[i][0] = vis[i][1] = vis[i][2] = vis[i][3] = false;
    }
    scanf("%d%d%d%d", &s1, &s2, &t1, &t2);
    que.push_back({{s1, s2}, 0});
    bool flag = false;
    while(que.size()) {
        auto t = que.front(); que.pop_front();
        int x = t.first.first, y = t.first.second;
        if(x == t1 && y == t2) {
            flag = true;
            printf("%d\n", t.second);
            break;
        }
        for(int j = 0; j < 4; ++j) {
            if(mp[y][j] == 0 || vis[y][j]) {
                continue;
            } else if(mp[y][j] == x) {  // 确定方向后拓展节点
                vis[y][j] = true;
                que.push_front({{y, mp[y][(j+1)%4]}, t.second});  // 将当前经过距离最短的放在队首,优先拓展
                que.push_back({{y, mp[y][(j+2)%4]}, t.second+1});
                que.push_back({{y, mp[y][(j+3)%4]}, t.second+1});
                que.push_back({{y, mp[y][j%4]}, t.second+1});
            }
        }
    }
    if(!flag) {
        printf("-1\n");
    }
    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值