“蔚来杯“2022牛客暑期多校训练营3

这篇博客包含三道算法竞赛题目,涉及字符串排序、最短路径规划和树上最近公共祖先问题。第一题通过排序解决字符串拼接问题,第二题使用BFS寻找最少红灯等待次数,第三题利用树剖计算LCA比较权值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

"蔚来杯"2022牛客暑期多校训练营3

C Concatenation

题目大意

给定n个字符串,求一个将他们拼接起来的方案,使得结果的字典序最小。

题解

对n个字符串排序,a比b小的条件是a+b<b+a。

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e6 + 6;
bool cmp(const string &a, const string &b)
{
    return a + b < b + a;
}
string s[maxn];
int n;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n;
    for (int i = 0; i < n; i++)
        cin >> s[i];
    sort(s, s + n, cmp);
    for (int i = 0; i < n; i++)
    {
        cout << s[i];
    }
    return 0;
}

J Journey

题目大意

给定一个城市有若干十字路口,右转不需要等红灯,直行、左转和掉头都需要,求起点到终点最少等几次红灯。

题解

建图:把每个十字路口的四个方向的路口信息存储在一个数组中。

然后 bfs 求出最短路径。

bfs的细节:

1、定义一个dis数组,存储从起点到当前点的距离信息(例如dis[i] [j]表示从与j路口相邻的第i个路口这个方向,到j这个路口,距起点的距离);定义一个vis数组,存储入队信息(spfa),vis数组表示的信息类似于dis数组。

2、初始化dis[相对于s2,s1在s2的哪个方向] [s2]为0,vis[相对于s2,s1在s2的哪个方向] [s2]为1。

3、对于队首元素,遍历它的每个方向,更新dis和vis,即spfa。

4、最后如果dis[相对于t2,t1在t2的哪个方向] [t2]更新过,则输出答案,否则输出-1。

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 5;
const int Max = 0x3f3f3f3f;
typedef pair<int, int> P;
int n, s1, s2, t1, t2, dir1, dir2;
struct Node
{
    int a[5];
} e[maxn];
int dis[5][maxn];
bool vis[5][maxn];
void bfs()
{
    queue<P> q;
    for (int i = 1; i <= 4; i++)
        if (e[s2].a[i] == s1)
            dir1 = i;
    for (int i = 1; i <= 4; i++)
        if (e[t2].a[i] == t1)
            dir2 = i;
    q.push(P(dir1, s2));
    vis[dir1][s2] = 1;
    dis[dir1][s2] = 0;
    while (!q.empty())
    {
        P now = q.front();
        q.pop();
        int start = now.second, tdir = now.first;
        vis[tdir][start] = 0;
        for (int i = 1; i <= 4; i++)
        {
            int w = 1, v = e[start].a[i], vdir;
            if (v == 0)
                continue;
            if (i == tdir % 4 + 1)
                w = 0;
            for (int j = 1; j <= 4; j++)
                if (e[v].a[j] == start)
                    vdir = j;
            if (dis[vdir][v] > dis[tdir][start] + w)
            {
                dis[vdir][v] = dis[tdir][start] + w;
                if (!vis[vdir][v])
                    q.push(P(vdir, v)), vis[vdir][v] = 1;
            }
        }
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    memset(dis, Max, sizeof dis);
    cin >> n;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= 4; j++)
            cin >> e[i].a[j];
    cin >> s1 >> s2 >> t1 >> t2;
    bfs();
    if (dis[dir2][t2] != Max)
        cout << dis[dir2][t2] << endl;
    else
        cout << -1 << endl;
    return 0;
}

A Ancestor

题目大意

给出两棵编号1-n的树A和B,A、B树上每个节点均有一个权值,给出k个关键点的编号x1…xn,问有多少种方案使得去掉恰好一个关键点使得剩余关键点在树A上LCA的权值大于树B上LCA的权值。

题解

树剖求LCA。

预处理出关键点序列在树A、B上的前缀LCA和后缀LCA,枚举去掉的关键节点并使用前后缀LCA算出剩余节点的LCA比较权值即可。

前缀LCA指前n个点的LCA,后缀LCA指后n个点的LCA。

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
int va[maxn], vb[maxn], x[maxn];
int n, k, res;
struct tree
{
    vector<int> g[maxn];
    int fa[maxn], size[maxn], top[maxn], mson[maxn], deep[maxn];
    int pre[maxn], lst[maxn];
    void add(int x, int y)
    {
        g[x].push_back(y);
    }
    void dfs(int x)
    {
        size[x] = 1;
        for (int i = 0; i < g[x].size(); i++)
        {
            int y = g[x][i];
            fa[y] = x;
            deep[y] = deep[x] + 1;
            dfs(y);
            size[x] += size[y];
            if (size[y] > size[mson[x]])
                mson[x] = y;
        }
    }
    void dfs(int x, int y)
    {
        top[x] = y;
        if (mson[x])
            dfs(mson[x], y);
        for (int i = 0; i < g[x].size(); i++)
        {
            int y = g[x][i];
            if (y == mson[x])
                continue;
            dfs(y, y);
        }
    }
    int lca(int x, int y)
    {
        while (top[x] != top[y])
        {
            if (deep[top[x]] < deep[top[y]])
                swap(x, y);
            x = fa[top[x]];
        }
        if (deep[x] < deep[y])
            return x;
        return y;
    }
    void predo()
    {
        dfs(1);
        dfs(1, 1);
        pre[1] = x[1];
        for (int i = 2; i <= k; i++)
        {
            pre[i] = lca(pre[i - 1], x[i]);
        }
        lst[k] = x[k];
        for (int i = k - 1; i >= 1; i--)
        {
            lst[i] = lca(lst[i + 1], x[i]);
        }
    }
    int getlca(int x)
    {
        if (x == 1)
            return lst[2];
        if (x == k)
            return pre[k - 1];
        return lca(pre[x - 1], lst[x + 1]);
    }
} a, b;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> k;
    for (int i = 1; i <= k; i++)
        cin >> x[i];
    for (int i = 1; i <= n; i++)
        cin >> va[i];
    for (int i = 2; i <= n; i++)
    {
        int fa;
        cin >> fa;
        a.add(fa, i);
    }
    for (int i = 1; i <= n; i++)
        cin >> vb[i];
    for (int i = 2; i <= n; i++)
    {
        int fb;
        cin >> fb;
        b.add(fb, i);
    }
    a.predo();
    b.predo();
    for (int i = 1; i <= k; i++)
    {
        int wa = a.getlca(i), wb = b.getlca(i);
        if (va[wa] > vb[wb])
            res++;
    }
    cout << res << endl;
}

F Fief

题目大意

给定一个无向图,每次询问两点x、y,求是否存在一个n的排列,使得第一个元素为x,最后一个元素为y,且排列的任意一个前缀、任意一个后缀都连通(前缀不包括y,后缀不包括x)。

题解

代码

未完

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值