2024牛客暑期多校训练营2

A. Floor Tiles

Problem

给定两种不同图案的地砖,问怎么拼接成大小为 N ⋅ M N\cdot M NM,且只有 K K K 条连续曲线,同时要求第 x x x 行,第 y y y 列地砖的图案为 t   ( t ∈ { ′ A ′ , ′ B ′ } ) t\ (t\in \{'A','B'\}) t (t{A,B})

数据范围: 1 ≤ N , M ≤ 800 , 1 ≤ K ≤ 2 ⋅ N ⋅ M 1\le N,M\le 800,1\le K\le 2\cdot N\cdot M 1N,M8001K2NM

Solution

首先可以发现最小的连续曲线的数量是全放同一种图案,此时共有 N + M N+M N+M条曲线;而数量最多的连续曲线可以通过构造 A B B A \begin{matrix} A & B \\ B & A \end{matrix} ABBA 获得内部圆的方式得到。

如果左上角为 A A A,有 N + M + ⌊ ( N − 1 ) ( M − 1 ) + 1 2 ⌋ N+M+\lfloor \frac{(N-1)(M-1)+1}{2}\rfloor N+M+2(N1)(M1)+1

如果左上角为 B B B,有 N + M + ⌊ ( N − 1 ) ( M − 1 ) 2 ⌋ N+M+\lfloor \frac{(N-1)(M-1)}{2}\rfloor N+M+2(N1)(M1)

因此对小于下界或大于上界的 K K K,直接输出 ′ N o ′ 'No' No,而在范围内的 K K K 可以先通过给定的瓷砖图案构造出最多的连续曲线,再将多余的曲线去掉即可。去掉多余曲线时可以选择用给定的图案覆盖即可。

时间复杂度: O ( N M ) O(NM) O(NM)

Code

#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <cmath>
#define ll long long
using namespace std;
const int N = 1e5;
int T;
int n, m, k, x, y;
char t, ans[1000][1000];
void solve()
{
    cin >> n >> m >> k;
    cin >> x >> y >> t;
    // printf("%c", t);
    int mi = n + m, ma = n + m + ((n - 1) * (m - 1) + 1) / 2;
    int p = 0;
    if ((t == 'A' && (x + y) % 2 == 1) || (t == 'B' && (x + y) % 2 == 0))
    {
        p = 1;
        ma = n + m + (n - 1) * (m - 1) / 2;
    }
    if (k > ma || k < mi)
    {
        printf("No\n");
        return;
    }
    k = ma - k;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            if ((i + j) % 2 == p)
            {
                ans[i][j] = 'A';
            }
            else
            {
                ans[i][j] = 'B';
            }
        }
    }
    for (int i = 1; i < n; i++)
    {
        for (int j = 1; j < m; j++)
        {
            if (ans[i][j] == 'A' && ans[i][j + 1] == 'B' && k > 0)
            {
                ans[i][j] = t;
                ans[i][j + 1] = t;
                k--;
            }
        }
    }
    printf("Yes\n");
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            printf("%c", ans[i][j]);
        }
        printf("\n");
    }
}
int main()
{
    cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}

B. MST

Problem

给定 n n n 个顶点和 m m m 条边,以及 q q q 次询问,每次询问给定 k k k 个不同的顶点,问由这 k k k 个顶点构成的子图的最小生成树的边权之和是多少。

数据范围: 2 ≤ n ≤ 1 0 5 , 1 ≤ m , q ≤ 1 0 5 , ∑ k ≤ 1 0 5 2\le n\le 10^5,1\le m,q\le 10^5,\sum k \le 10^5 2n105,1m,q105,k105

Solution

该问题主要是需要解决快速找到子图的边,如果每次遍历所有边,复杂度为 O ( q m ) O(qm) O(qm),如果每次遍历边的两个顶点,复杂度为 O ( ∑ k 2 ) O(\sum k^2) O(k2),这两种方法显然不行,不过将这两种方法结合起来就可以解决该问题。

k > n k > \sqrt{n} k>n 的情况,通过遍历所有边找到子图的边,复杂度 O ( m n ) O(m\sqrt{n}) O(mn );对 k ≤ n k\le \sqrt{n} kn 的情况,通过遍历两个顶点的找到子图的边,复杂度为 O ( n n ) O(n\sqrt{n}) O(nn )

将得到的子图的边用 k r u s k a l kruskal kruskal 算法求最小生成树即可。 k r u s k a l kruskal kruskal 算法求最小生成树的复杂度为 O ( m   l o g m ) O(m\ logm) O(m logm)

Code

#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <cmath>
#define ll long long
using namespace std;
const int N = 1e5;
int n, m, q, k;
int f[N + 5], a[N + 5];
struct edge
{
    int u, v;
    ll w;
    edge(int a, int b, ll c)
    {
        u = a, v = b, w = c;
    }
};
int find(int x)
{
    return x == f[x] ? x : f[x] = find(f[x]);
}
bool merge(int x, int y)
{
    x = find(x);
    y = find(y);
    if (x == y)
    {
        return false;
    }
    f[y] = x;
    return true;
}
bool cmp(edge x, edge y)
{
    return x.w < y.w;
}
int main()
{
    cin >> n >> m >> q;
    int lim = sqrt(n);
    vector<edge> G;
    map<pair<int, int>, ll> mp;
    for (int i = 0; i < m; i++)
    {
        int u, v;
        ll w;
        cin >> u >> v >> w;
        if (u < v)
        {
            swap(u, v);
        }
        G.push_back(edge(u, v, w));
        mp[{u, v}] = w;
    }
    for (int i = 1; i <= q; i++)
    {
        cin >> k;
        for (int j = 1; j <= k; j++)
        {
            cin >> a[j];
            f[a[j]] = a[j];
        }
        int cnt = 0;
        ll ans = 0;
        vector<edge> Q;
        if (k <= lim)
        {
            for (int x = 1; x <= k; x++)
            {
                for (int y = x + 1; y <= k; y++)
                {
                    int u = a[x], v = a[y];
                    if (u < v)
                    {
                        swap(u, v);
                    }
                    if (mp.count({u, v}))
                    {
                        Q.push_back(edge(u, v, mp[{u, v}]));
                    }
                }
            }
        }
        else
        {
            for (int j = 0; j < m; j++)
            {
                int u = G[j].u, v = G[j].v;
                ll w = G[j].w;
                if (f[u] && f[v])
                {
                    Q.push_back(edge(u, v, w));
                }
            }
        }
        sort(Q.begin(), Q.end(), cmp);
        for (int i = 0; i < Q.size(); i++)
        {
            int u = Q[i].u, v = Q[i].v;
            if (merge(u, v))
            {
                ans += mp[{u, v}];
                cnt++;
            }
        }
        for (int j = 1; j <= k; j++)
        {
            f[a[j]] = 0;
        }
        if (cnt == k - 1)
        {
            printf("%lld\n", ans);
        }
        else
        {
            printf("-1\n");
        }
    }
    return 0;
}

C. Red Walking on Grid

Problem

给定一个 2 × n 2\times n 2×n的网格,网格上要么为红色要么为白色,要求只能在红色网格上下左右移动,同时每个位置不能重复经过,问最多能走多少步。

数据范围: 1 ≤ n ≤ 1 0 6 1\le n\le 10^6 1n106

Solution

将每行连续的颜色为红色的网格作为区间提取出来,并按左端点排序。对于两个区间 [ l i , r i ] , [ l j , r j ] [l_i,r_i],[l_j,r_j] [li,ri][lj,rj],只有两种情况需要特殊考虑。

l i < l j ≤ r j < r i l_i<l_j\le r_j<r_i li<ljrj<ri 时,即两个区间包含,并且 r j − l j + 1 r_j-l_j+1 rjlj+1 为奇数时,答案要减一。

l i < l j < r i < r j l_i<l_j<r_i <r_j li<lj<ri<rj时,即两个区间相交,并且 r i − l j + 1 r_i-l_j+1 rilj+1 为偶数时,答案要减一。

对于其他情况一律加上区间长度即可。

时间复杂度: O ( n   l o g n ) O(n\ logn) O(n logn)

Code

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 5e3;
const ll M = 1000000007;
int n;
string s[2];
int main()
{
    cin >> n;
    cin >> s[0] >> s[1];
    s[0] = s[0] + 'W';
    s[1] = s[1] + 'W';
    vector<pair<int, int>> p;
    for (int j = 0; j < 2; j++)
    {
        int len = 0, l = 0;
        for (int i = 0; i <= n; i++)
        {
            if (s[j][i] == 'R')
            {
                len++;
            }
            else
            {
                if (len > 0)
                {
                    p.push_back({l, l + len - 1});
                }
                len = 0;
                l = i + 1;
            }
        }
    }
    sort(p.begin(), p.end());
    int ans = 0, cur = 0, ma = -1, pre = -1;
    for (int i = 0; i < p.size(); i++)
    {
        // printf("%d %d\n", p[i].first, p[i].second);
        if (p[i].first > ma)
        {
            ans = max(ans, cur);
            cur = p[i].second - p[i].first + 1;
        }
        else
        {
            cur += p[i].second - p[i].first + 1;
            if (p[i].second < ma && (p[i].second - p[i].first + 1) % 2 == 1 && p[i].first != pre)
            {
                cur--;
            }
            if (p[i].second > ma && (ma - p[i].first + 1) % 2 == 0 && p[i].first != pre)
            {
                cur--;
            }
        }
        ma = max(ma, p[i].second);
        pre = p[i].first;
    }
    ans = max(ans, cur);
    printf("%d\n", max(0, ans - 1));
    return 0;
}

E. GCD VS XOR

Problem

给定整数 x x x,要求找到一个整数 y   ( 0 < y < x ) y\ (0<y<x) y (0<y<x),使得 g c d ( x , y ) = x ⊕ y gcd(x,y)=x\oplus y gcd(x,y)=xy

数据范围: 1 ≤ x ≤ 1 0 18 1\le x\le 10^{18} 1x1018

Solution

x x x 为奇数时, y = x − 1 y=x-1 y=x1 显然能符合条件,当 x x x 为偶数时,将 x x x 表示成二进制形式后,发现将最后一个为 1 1 1 的位减去后也可以满足条件。

因此答案即为 x − l o w b i t ( x ) x-lowbit(x) xlowbit(x),其中为 l o w b i t ( x ) = x   & − x lowbit(x)=x\ \&-x lowbit(x)=x &x

l o w b i t ( x ) = x lowbit(x)=x lowbit(x)=x 时,不存在这样的非负整数,输出 − 1 -1 1 即可。

时间复杂度: O ( 1 ) O(1) O(1)

Code

#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <cmath>
#define ll long long
using namespace std;
const int N = 1e5;
int T;
ll lowbit(ll x)
{
    return x & -x;
}
void solve()
{
    ll x;
    cin >> x;
    ll y = x - lowbit(x);
    if (y == 0)
    {
        printf("-1\n");
    }
    else
    {
        printf("%lld\n", y);
    }
}
int main()
{
    cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}

H. Instructions Substring

Problem

给定一个长度为 n n n 的字符串,只包含 W 、 S 、 A 、 D W、S、A、D WSAD 这四个大写字母,表示向上下左右移动一个单位,初始时位于 ( 0 , 0 ) (0,0) (0,0),问有多少个连续子串能够经过 ( x , y ) (x,y) (x,y)

数据范围: 1 ≤ n ≤ 2 × 1 0 5 , − 1 0 5 ≤ x , y ≤ 1 0 5 1\le n\le 2\times10^5,-10^5\le x,y\le 10^5 1n2×105,105x,y105

Solution

因为要求的是能够经过 ( x , y ) (x,y) (x,y),假如子串区间为 [ l , r ] [l,r] [l,r] 恰好能到达点 ( x , y ) (x,y) (x,y),那么所有左端点为 l l l,右端点大于 r r r 的子串都是符合条件的。

因此可以通过从后往前遍历解决,对于当前的左端点 i i i,找到恰好到达 ( x , y ) (x,y) (x,y) 的右端点 j j j a n s = a n s + n − j + 1 ans=ans+n-j+1 ans=ans+nj+1

时间复杂度: O ( n   l o g n ) O(n\ logn) O(n logn)

Code

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e6;
const ll M = 1000000007;
int n, x, y;
string s;
int main()
{
    cin >> n >> x >> y;
    cin >> s;
    if (x == 0 && y == 0)
    {
        printf("%lld\n", (ll)n * (n + 1) / 2);
        return 0;
    }
    map<pair<int, int>, ll> mp;
    mp[{0, 0}] = n;
    int nx = 0, ny = 0;
    ll ans = 0;
    for (int i = n - 1; i >= 0; i--)
    {
        if (s[i] == 'W')
        {
            ny++;
        }
        else if (s[i] == 'A')
        {
            nx--;
        }
        else if (s[i] == 'S')
        {
            ny--;
        }
        else
        {
            nx++;
        }
        //printf("%d %d\n", nx, ny);
        if (mp.count({nx - x, ny - y}))
        {
            ans += n - mp[{nx - x, ny - y}] + 1;
        }
        mp[{nx, ny}] = i;
    }
    printf("%lld\n", ans);
    return 0;
}

I. Red Playing Cards

Problem

给定长度为 2 ⋅ n 2\cdot n 2n 的数组 { a } i = 1 2 n \{a\}_{i=1}^{2n} {a}i=12n,其中 1 ∼ n 1\sim n 1n 每个元素恰好出现两次。每次可以选择一个整数 i i i,找到数组中等于 i i i 的位置,记为 l i , r i l_i,r_i li,ri,然后移除区间 [ l i , r i ] [l_i,r_i] [li,ri] 范围内的所有元素,同时得分增加 i × ( r i − l i + 1 ) i\times(r_i-l_i+1) i×(rili+1),问最大可以获得多少。

数据范围: 1 ≤ n ≤ 3000 1\le n\le 3000 1n3000

Solution

f i f_i fi 表示数 i i i 的区间 [ l i , r i ] [l_i,r_i] [li,ri] 可以获得的最大分数,如果区间内的数都比 i i i 小,那么直接将这个区间移除是最优的,如果区间内存在一对数 j ( j > i ) j(j>i) j(j>i),那么应该先移除 [ l j , r j ] [l_j,r_j] [lj,rj],再移除 [ l i , r i ] [l_i,r_i] [li,ri],是更优的策略。

因此可以通过 d p dp dp 来求解 f i f_i fi,记 g k g_k gk 表示区间 [ l i , k ] [l_i,k] [li,k] 的最大分数,状态转移方程如下:

如果 k = r j k=r_j k=rj,并且 l j > l i l_j>l_i lj>li g k = m a x ( g k − 1 + i , g l j − 1 + f j ) g_k=max(g_{k-1}+i,g_{l_j-1}+f_j) gk=max(gk1+i,glj1+fj)

其他情况下, g k = g k − 1 + i g_k=g_{k-1}+i gk=gk1+i

因为只有比数 i i i 大的数才会对 f i f_i fi 有影响的,因此从大到小求解 f i f_i fi 即可,可以在数组两端加上值为 0 0 0 的元素,那么最后的答案即为 f 0 f_0 f0

时间复杂度: O ( n 2 ) O(n^2) O(n2)

Code

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e6;
const ll M = 1000000007;
int n, a[N + 5], f[N + 5], g[N + 5], l[N], r[N];
int main()
{
    cin >> n;
    for (int i = 1; i <= 2 * n; i++)
    {
        cin >> a[i];
        r[a[i]] = i;
    }
    for (int i = 2 * n; i >= 1; i--)
    {
        l[a[i]] = i;
    }
    a[0] = 0, a[2 * n + 1] = 0;
    l[0] = 0, r[0] = 2 * n + 1;
    for (int i = n; i >= 0; i--)
    {
        for (int i = 0; i <= 2 * n + 1; i++)
        {
            g[i] = 0;
        }
        for (int j = l[i] + 1; j < r[i]; j++)
        {
            if (j == r[a[j]] && l[a[j]] > l[i])
            {
                g[j] = max(g[j - 1] + i, g[l[a[j]] - 1] + f[a[j]]);
            }
            else
            {
                g[j] = g[j - 1] + i;
            }
        }
        f[i] = g[r[i] - 1] + 2 * i;
        // printf("%d\n", f[i]);
    }
    printf("%d\n", f[0]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值