【算法题解】2022河南萌新联赛第(四)场:郑州轻工业大学

A 并查集

🍔 题目

image-20220731215138995

🌭 思路:并发集

将每次遇到可以连边的都进行合并即可。如何合并呢?由于合并是没有顺序的,我们可以直接将遇到的第一个数合并即可。

  • 遇到的第一个Z和后面的Z U L I 合并
  • 将遇到的第一个U和的U L I合并
  • 其他同理

🔨 代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
typedef priority_queue<int, vector<int>, less<int>> Q;
#define x first
#define y second
#define endl '\n'
#define ppb pop_back
#define pb push_back
#define pf push_front
#define YES cout << "YES" << endl
#define Yes cout << "Yes" << endl
#define yes cout << "yes" << endl
#define NO cout << "NO" << endl
#define No cout << "No" << endl
#define no cout << "no" << endl
#define all(x) x.begin(), x.end()
#define rall(x) x.rbegin(), x.rend()
#define mset(x, a) memset(x, a, sizeof(x))
#define rep(i, l, r) for (LL i = l; i <= (r); ++i)
#define per(i, r, l) for (LL i = r; i >= (l); --i)
const int N = 1e6 + 10, inf = 0x3f3f3f3f, mod = 998244353;
char s[N];
int p[N];
int find(int x)
{
    if (p[x] != x)
        p[x] = find(p[x]);
    return p[x];
}
int v[10]; // Z U L I
void solve()
{
    scanf("%s", s + 1);
    int n = strlen(s + 1);
    for (int i = 1; i <= n; i++)
        p[i] = i;
    for (int i = 1; i <= n; i++)
    {
        if (s[i] == 'Z')
        {
            if (v[0] == 0)
            {
                v[0] = i;
            }
            else
                p[find(v[0])] = i;
        }
        else if (s[i] == 'U')
        {
            if (v[0])
                p[find(v[0])] = i;
            if (v[1] == 0)
                v[1] = i;
            else
                p[find(v[1])] = i;
        }
        else if (s[i] == 'L')
        {
            if (v[0])
                p[find(v[0])] = i;
            if (v[1])
                p[find(v[1])] = i;
            if (v[2] == 0)
                v[2] = i;
            else
                p[find(v[2])] = i;
        }
        else if (s[i] == 'I')
        {
            if (v[0])
                p[find(v[0])] = i;
            if (v[1])
                p[find(v[1])] = i;
            if (v[2])
                p[find(v[2])] = i;
            if (v[3] == 0)
                v[3] = i;
            else
                p[find(v[3])] = i;
        }
    }
    int ans = 0;
    map<int, int> ma;
    for (int i = 1; i <= n; i++)
    {
        int t = find(i);
        ma[t]++;
        ans = max(ans, ma[t]);
    }
    cout << ans;
}
signed main()
{
#ifdef Xin
    freopen("in.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif
    int T = 1;
    while (T--)
        solve();
    return 0;
}

C 最大公因数

🌊 题目

image-20220731215913854

📛 思路:数学

a和b的最大公约数是x,那么a和b一定是x的倍数并且a/xa/b互质,直接找到比l大的相邻两个x的倍数即可,它俩除于x后差是1,一定互质。然后看它俩在不在[l,r]区间内。

🗡 代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10, inf = 0x3f3f3f3f, mod = 998244353;
void solve()
{
    int l, r, x;
    cin >> l >> r >> x;
    int a = (l + x - 1) / x * x;
    int b = a + x;
    if (a >= l && a <= r && b >= l && b <= r)
        cout << a << " " << b << endl;
    else
        puts("-1");
}
signed main()
{
#ifdef Xin
    freopen("in.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif
    int T = 1;
    cin >> T;
    while (T--)
        solve();
    return 0;
}

D 大盗

📫 题目

image-20220731223429196

🐤 思路:模拟

我们先想想暴力怎么写,我们现在有一个集合s,存可能获得到的重量和,枚举每一个房间,如果是藏品,我们可以让集合里的每个数加上a[i]存入s中,集合原来的数保留。如果是三体人,那么集合中只保留重量和为a[i]的,剩下的都不要了,如果没有a[i]就全部不要了。但是这题的数据范围根本没办法暴力写,一定会超时。

那我们可以用bitset这个容器,这个容器就是用来优化时间复杂度的。

关于bisset可以看这篇博客:c++中Bitset用法

⛵️ 代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
typedef priority_queue<int, vector<int>, less<int>> Q;
#define x first
#define y second
#define endl '\n'
#define ppb pop_back
#define pb push_back
#define pf push_front
#define YES cout << "YES" << endl
#define Yes cout << "Yes" << endl
#define yes cout << "yes" << endl
#define NO cout << "NO" << endl
#define No cout << "No" << endl
#define no cout << "no" << endl
#define all(x) x.begin(), x.end()
#define rall(x) x.rbegin(), x.rend()
#define mset(x, a) memset(x, a, sizeof(x))
#define rep(i, l, r) for (LL i = l; i <= (r); ++i)
#define per(i, r, l) for (LL i = r; i >= (l); --i)
const int N = 1e6 + 10, inf = 0x3f3f3f3f, mod = 998244353;
ll qmi(int a, int b)
{
    ll res = 1 % mod;
    while (b)
    {
        if (b & 1)
            res = res * a % mod;
        a = a * (ll)a % mod;
        b >>= 1;
    }
    return res;
}
ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }
int dx[4] = {1, -1, 0, 0};
int dy[4] = {0, 0, 1, -1};
void solve()
{
    int n, m;
    cin >> n >> m;
    bitset<50001> s;
    s.set(0);
    for (int i = 1; i <= n; i++)
    {
        int op, x;
        cin >> op >> x;
        if (op == 1)
        {
            s |= (s << x);
        }
        else
        {
            bool ok = false;
            if (s.test(x))
                ok = true;
            s.reset();
            s.set(0);
            if (ok)
                s.set(x);
        }
    }
    for (int i = m; i >= 0; i--)
    {
        if (s.test(i))
        {
            cout << i;
            return;
        }
    }
}
signed main()
{
#ifdef Xin
    freopen("in.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif
    int T = 1;
    while (T--)
        solve();
    return 0;
}

E 睡大觉

👹 题目

image-20220731231044819

✌️ 思路:模拟

由于睡觉至少需要1秒,那么如果当前睡醒时间比前一次睡醒时间小于等于的话就需要加一天,否则就还在前一次睡醒时间的那一天,直接模拟即可。

🎅 代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
typedef priority_queue<int, vector<int>, less<int>> Q;
#define x first
#define y second
#define endl '\n'
#define ppb pop_back
#define pb push_back
#define pf push_front
#define YES cout << "YES" << endl
#define Yes cout << "Yes" << endl
#define yes cout << "yes" << endl
#define NO cout << "NO" << endl
#define No cout << "No" << endl
#define no cout << "no" << endl
#define all(x) x.begin(), x.end()
#define rall(x) x.rbegin(), x.rend()
#define mset(x, a) memset(x, a, sizeof(x))
#define rep(i, l, r) for (LL i = l; i <= (r); ++i)
#define per(i, r, l) for (LL i = r; i >= (l); --i)
const int N = 1e6 + 10, inf = 0x3f3f3f3f, mod = 998244353;
int d[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int year, month, day;
int ans;
void update()
{
    day++;
    int days = d[month]; //本月的总天数
    if (month == 2)
    {
        if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
            days++;
    }
    if (day > days)
    {
        day = 1;
        month++;
    }
    if (month > 12)
    {
        year++;
        month = 1;
    }
}
void solve()
{
    scanf("%d-%d-%d", &year, &month, &day);
    int q;
    cin >> q;
    int last = 0;
    while (q--)
    {
        int h, m, s;
        scanf("%d:%d:%d", &h, &m, &s);
        int now = h * 60 * 60 + m * 60 + s;
        if (now <= last)
            update();
        last = now;
        if (month % 2 == day % 2)
            ans++;
    }
    cout << ans;
    return;
}
signed main()
{
#ifdef Xin
    freopen("in.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif
    int T = 1;
    while (T--)
        solve();
    return 0;
}

I 密集

😆 题目

image-20220731234518162

💃 思路:二分

如果说时间越长,那么退赛的蜗牛可能越多,越满足条件,所以满足单调性,而且题目问的至少,那就可以往二分这个角度去想。我们二分时间,假设有两只蜗牛a和b,a在前b在后,那么a要想超过b肯定a在x时间后的位置要比b大,b被超过就会退出。计算最后剩下了多少只蜗牛,看是否小于等于k即可。

👊 代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> PII;
typedef priority_queue<int, vector<int>, less<int>> Q;
#define x first
#define y second
#define endl '\n'
#define ppb pop_back
#define pb push_back
#define pf push_front
#define YES cout << "YES" << endl
#define Yes cout << "Yes" << endl
#define yes cout << "yes" << endl
#define NO cout << "NO" << endl
#define No cout << "No" << endl
#define no cout << "no" << endl
#define all(x) x.begin(), x.end()
#define rall(x) x.rbegin(), x.rend()
#define mset(x, a) memset(x, a, sizeof(x))
#define rep(i, l, r) for (LL i = l; i <= (r); ++i)
#define per(i, r, l) for (LL i = r; i >= (l); --i)
const int N = 1e6 + 10, inf = 0x3f3f3f3f, mod = 998244353;
int dx[4] = {1, -1, 0, 0};
int dy[4] = {0, 0, 1, -1};
PII a[N];// fitst存位置,second存速度
int n, k;
bool check(ll x)
{
    int cnt = 0;
    ll last = a[1].x + a[1].y * x;
    cnt++;
    for (int i = 2; i <= n; i++)
    {
        ll now = a[i].x + a[i].y * x;
        // 被超过
        if (now < last)
            continue;
        last = now;
        cnt++;
    }
    return cnt <= k;
}
void solve()
{
    cin >> n >> k;
    for (int i = 1; i <= n; i++)
        cin >> a[i].x;
    for (int i = 1; i <= n; i++)
        cin >> a[i].y;
    // 按照位置排序
    sort(a + 1, a + n + 1);
    ll l = 0, r = 2e9;
    while (l < r)
    {
        ll mid = (l + r) >> 1;
        if (check(mid))
            r = mid;
        else
            l = mid + 1;
    }
    if (r == 2e9)
        puts("Never!");
    else
        cout << r;
    return;
}
signed main()
{
#ifdef Xin
    freopen("in.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif
    int T = 1;
    while (T--)
        solve();
    return 0;
}

G 迷宫

🍨 题目

image-20220731204834316

🎃 思路:双端队列广搜

每个点到走到普通的点的花费是1秒,到花朵的花费是0秒(走1秒在减去1秒),所以本题就是一道经典的双端队列广搜。

双端队列广搜

概念:双端队列广搜其实就是求最短路的简化版,这里边权只有0和1,所以不需要像dijkstra算法那样还要排序找最小值,直接将小的放入队头,大的放入队尾,则此时队列一定是前面全是边权为0的,后面全是边权为1的。这样队列永远都是一个已经排好序的队列。

与堆优化版的dijkstra一样,双端队列广搜必须在出队列的时候才能确定为每个点的距离最小值,但是又和一般的bfs不一样,因为这里边权不同。举个例子:

image-20220731205928834

当前出队的是1号点,则2号点和3号点的最短距离都会被更新,但此时3号点的最短距离不是真正得最短距离,还需要通过2号点来更新,现在将2号点放入队头,3号点放入队尾。下一步会取出2号点,然后再一次更新3号点,因为此时距离最短。这里我们可以看出,边权不统一得时候,更新其他点得距离并不是最短距离,出队得时候才是最短距离。我们一定要记住只要最短距离更新了就入队,第一次更新不一定是最短距离。

所以本题中,只要遇到路花费就+1放入队尾, 遇到花朵就直接放入队头。

🐴 队尾

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
typedef priority_queue<int, vector<int>, less<int>> Q;
#define x first
#define y second
const int N = 2010, inf = 0x3f3f3f3f, mod = 998244353;
int dx[4] = {1, -1, 0, 0};
int dy[4] = {0, 0, 1, -1};
char g[2010][2010];
int dist[2010][2010];
int n, m;
void solve()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        scanf("%s", g[i] + 1);
    deque<PII> q;
    memset(dist, 0x3f, sizeof dist);
    dist[1][1] = 0;
    q.push_back({1, 1});
    while (q.size())
    {
        auto t = q.front();
        q.pop_front();
        int x = t.x, y = t.y;
        for (int i = 0; i < 4; i++)
        {
            int tx = x + dx[i];
            int ty = y + dy[i];
            if (tx < 1 || tx > n || ty < 1 || ty > m)
                continue;
            if (g[tx][ty] == '#')
                continue;
            if (g[tx][ty] == '*')
            {
                if (dist[tx][ty] > dist[x][y])
                {
                    dist[tx][ty] = dist[x][y];
                    q.push_front({tx, ty});
                }
            }
            else
            {
                if (dist[tx][ty] > dist[x][y] + 1)
                {
                    dist[tx][ty] = dist[x][y] + 1;
                    q.push_back({tx, ty});
                }
            }
        }
    }
    if (dist[n][m] != inf)
        cout << dist[n][m];
    else
        cout << -1;
    return;
}
signed main()
{
#ifdef Xin
    freopen("in.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif
    int T = 1;
    while (T--)
        solve();
    return 0;
}

J 苹方树

📦 题目

image-20220731211447191

思路:树上差分

关于树上差分的详细讲解可以看这篇博客 【夯实算法基础】最近公共祖先

如果这道题改成一颗树的子树中包含了一条路径的一个节点乘上一次lj,包含一条路径的两个节点在乘上一次lj,那这题就好做了,可是一个点及其子树最多只能被乘上一次lj怎么办呢,这里就可以想到树上差分。我们将一条路径的两个端点ab都乘上wlca(a,b)除于w,那么我们在计算一个点的子树总共被乘上多少的时候就不会多乘了。ab只对他们的祖先有用,当到了lca(a,b)的时候,在抵消一次,这样就没有问题了。最后用dfs求每个点及其子树的权值即可。

这里还有一个问题,我们是要求每个权值是不是平方数,如果每次乘或者除的话,值很大,如果用高精度的话非常麻烦,而且值很大。我们可以想一想平方数的性质,将每个数分解质因数,统计每个质因数的个数,如果质因数的个数为奇数个,那么就一定不是平放数。这里w最大为100,所以求w的质因数复杂度很低。

🏇 代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
typedef priority_queue<int, vector<int>, less<int>> Q;
#define x first
#define y second
#define endl '\n'
#define ppb pop_back
#define pb push_back
#define pf push_front
#define YES cout << "YES" << endl
#define Yes cout << "Yes" << endl
#define yes cout << "yes" << endl
#define NO cout << "NO" << endl
#define No cout << "No" << endl
#define no cout << "no" << endl
#define all(x) x.begin(), x.end()
#define rall(x) x.rbegin(), x.rend()
#define mset(x, a) memset(x, a, sizeof(x))
#define rep(i, l, r) for (LL i = l; i <= (r); ++i)
#define per(i, r, l) for (LL i = r; i >= (l); --i)
const int N = 2e5 + 10, M = N * 2, inf = 0x3f3f3f3f, mod = 998244353;
int n, m, q;
int h[N], e[M], ne[M], idx;
int d[N];     //深度
int f[N][21]; // lca中的祖先是谁
int w[N][30]; //每个点的质因数
int p[100];   //存质数
bool st[N];
int cnt = 0;
int id[100]; //每个质数对应的下标
bool ans[N]; //存每个节点的权值是不是平方数
// 求质数
void init()
{
    for (int i = 2; i <= 100; i++)
    {
        if (!st[i])
        {
            id[i] = cnt;
            p[cnt++] = i;
        }
        for (int j = 0; p[j] <= 100 / i; j++)
        {
            st[i * p[j]] = true;
            if (i % p[j] == 0)
                break;
        }
    }
}
void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void bfs()
{
    memset(d, 0x3f, sizeof d);
    d[0] = 0, d[1] = 1;
    queue<int> q;
    q.push(1);
    while (q.size())
    {
        int u = q.front();
        q.pop();
        for (int i = h[u]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (d[j] > d[u] + 1)
            {
                d[j] = d[u] + 1;
                q.push(j);
                f[j][0] = u;
                for (int k = 1; k <= 20; k++)
                {
                    f[j][k] = f[f[j][k - 1]][k - 1];
                }
            }
        }
    }
}
int lca(int a, int b)
{
    if (d[a] < d[b])
        swap(a, b);
    for (int i = 20; i >= 0; i--)
    {
        if (d[f[a][i]] >= d[b])
        {
            a = f[a][i];
        }
    }
    if (a == b)
        return a;
    for (int i = 20; i >= 0; i--)
    {
        if (f[a][i] != f[b][i])
        {
            a = f[a][i];
            b = f[b][i];
        }
    }
    return f[a][0];
}
void dfs(int u, int fa)
{
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (j == fa)
            continue;
        dfs(j, u);
        // 加上子树的
        for (int k = 0; k < cnt; k++)
        {
            w[u][k] += w[j][k];
        }
    }
    int f = 1;
    // 判断是不是平方数
    for (int i = 0; i < cnt; i++)
    {
        if (w[u][i] % 2 == 1)
        {
            f = 0;
        }
    }
    if (f == 1)
        ans[u] = true;
}
void solve()
{
    memset(h, -1, sizeof h);
    init();
    cin >> n;
    for (int i = 0; i < n - 1; i++)
    {
        int a, b;
        cin >> a >> b;
        add(a, b);
        add(b, a);
    }
    bfs();
    cin >> m;
    for (int i = 1; i <= m; i++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        int anc = lca(a, b);
        for (int j = 2; j <= c / j; j++)
        {
            if (c % j == 0)
            {
                while (c % j == 0)
                {
                    int e = id[j];
                    w[a][e]++;
                    w[b][e]++;
                    w[anc][e]--;
                    c /= j;
                }
            }
        }
        if (c > 1)
        {
            int e = id[c];
            w[a][e]++;
            w[b][e]++;
            w[anc][e]--;
        }
    }
    dfs(1, -1);
    cin >> q;
    while (q--)
    {
        int t;
        cin >> t;
        if (ans[t])
            puts("YES");
        else
            puts("NO");
    }
}
signed main()
{
#ifdef Xin
    freopen("in.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif
    int T = 1;
    while (T--)
        solve();
    return 0;
}

K 试香

🎃 题目

image-20220731210637161

🏮 思路:枚举

我们可以发现题目中说a[i]都是2得幂,那么就可以直接想到二进制,枚举x的二进制的每一位,查询是否有对应的a[i]出现即可

📛 代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10, inf = 0x3f3f3f3f, mod = 998244353;
void solve()
{
    ll n, x;
    cin >> n >> x;
    map<ll, int> ma;
    for (int i = 1; i <= n; i++)
    {
        ll t;
        cin >> t;
        ma[t] = i;
    }
    int f = 1;
    vector<int> v;
    for (ll i = 60; i >= 0; i--)
    {
        if (x >> i & 1)
        {
            ll t = 1ll << i;
            if (ma.count(t))
            {
                v.push_back(ma[t]);
            }
            else
            {
                f = 0;
                break;
            }
        }
    }
    if (f == 0)
        puts("-1");
    else
    {
        sort(v.begin(), v.end());
        cout << v.size() << endl;
        for (int i = 0; i < v.size(); i++)
            cout << v[i] << " ";
        cout << endl;
    }
}
signed main()
{
#ifdef Xin
    freopen("in.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif
    int T = 1;
    cin >> T;
    while (T--)
        solve();
    return 0;
}

L 固执

🍂 题目

image-20220801095859728

🎯 思路:贪心

如果想让一个字符串无论怎么排列都会有字符相同的话,那么一定要有一个字符的个数大于等于字符串长度n/2+1,又因为需要字典序最小并且每个后缀都满足条件,我们可以从后向前枚举每一个位置,在这个位置上看填什么符合要求,并且字典序要最后,反转过来就是字典序最小。

🐎 代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
typedef priority_queue<int, vector<int>, less<int>> Q;
#define x first
#define y second
#define endl '\n'
#define ppb pop_back
#define pb push_back
#define pf push_front
#define YES cout << "YES" << endl
#define Yes cout << "Yes" << endl
#define yes cout << "yes" << endl
#define NO cout << "NO" << endl
#define No cout << "No" << endl
#define no cout << "no" << endl
#define all(x) x.begin(), x.end()
#define rall(x) x.rbegin(), x.rend()
#define mset(x, a) memset(x, a, sizeof(x))
#define rep(i, l, r) for (LL i = l; i <= (r); ++i)
#define per(i, r, l) for (LL i = r; i >= (l); --i)
const int N = 1e6 + 10, inf = 0x3f3f3f3f, mod = 998244353;
int a[30];
void solve()
{
    int n;
    cin >> n;
    string s;
    cin >> s;
    for (int i = 0; i < n; i++)
        a[s[i] - 'a']++;
    string ans = "";
    // 记录数量最多的那个字符
    int last = 0;
    for (int i = 0; i < 26; i++)
    {
        if (a[i] > a[last])
            last = i;
    }
    // 最多的那个字符数量
    int sum = 0;
    for (int i = 1; i <= n; i++)
    {
        // 大于等于n/2+1了
        if (sum > i - sum)
        {
            bool ok = false;
            // 从字典序高到底枚举
            for (int j = 25; j >= 0; j--)
            {
                if (j != last && a[j])
                {
                    ok = true;
                    a[j]--;
                    ans += j + 'a';
                    break;
                }
            }
            if (!ok)
            {
                if (a[last])
                {
                    a[last]--;
                    ans += last + 'a';
                    sum++;
                }
                else
                {
                    cout << "NO";
                    return;
                }
            }
        }
        else
        {
            if (a[last])
            {
                ans += last + 'a';
                a[last]--;
                sum++;
            }
            else
            {
                cout << "NO";
                return;
            }
        }
    }
    cout << "YES\n";
    // 反转字符串
    reverse(ans.begin(), ans.end());
    cout << ans;
    return;
}
signed main()
{
#ifdef Xin
    freopen("in.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif
    int T = 1;
    while (T--)
        solve();
    return 0;
}

🍭 有疑问欢迎评论区留言哦

image-20220729114611343

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值