2024牛客暑期多校训练营3

A. Bridging the Gap 2

Problem

现在有 n n n 个人需要通过一艘船过河,给定每个人的体力为 h i h_i hi,以及该艘船最多能够承载 R R R 个人,至少需要 L L L 个人操作。

同时每乘坐一次船后体力变为 h i − 1 h_i-1 hi1,当体力变为 0 0 0 后就不能再乘坐船过河了。

问这 n n n 个人能否全部到达河对岸。

数据范围: 1 ≤ L < R ≤ n ≤ 5 × 1 0 5 , 1 ≤ h i ≤ 5 × 1 0 5 1\le L<R\le n\le 5\times10^5,1\le h_i\le 5\times 10^5 1L<Rn5×105,1hi5×105

Solution

最优的策略显然是每次选当前体力值最大的 R R R 个人过河,再选择河对面体力值最大的 L L L 个人将船运回来。

假设前 i i i 趟需要的需要的体力值为 x x x,而所有人目前能够提供的体力值为 y y y,那么判断前 i i i 趟能否进行的充要条件其实就是 x ≤ y x\le y xy

必要性是显然的,因为如果连这个条件都不满足那么前 i i i 趟是不可能进行的;而充分性采用最优的策略后可以通过数学归纳法证明。

那么只需要判断最后一趟是否满足条件即可。

t t t 表示总共的趟数,那么 x = ( t − 1 ) ( R + L ) + n − ( t − 1 ) ( R − L ) x=(t-1)(R+L)+n-(t-1)(R-L) x=(t1)(R+L)+n(t1)(RL),要注意最后过河的人数可能不足 R R R 个。

h i h_i hi 为奇数时,能提供 m i n ( 2 ( t − 1 ) + 1 , h i ) min(2(t-1)+1,h_i) min(2(t1)+1,hi)

h i h_i hi 为偶数时,能提供 m i n ( 2 ( t − 1 ) + 1 , h i − 1 ) min(2(t-1)+1,h_i-1) min(2(t1)+1,hi1)

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

Code

#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <cmath>
#define ll long long
using namespace std;
const int N = 5e5;
int n, l, r;
int h[N + 5];
int main()
{
    cin >> n >> l >> r;
    for (int i = 1; i <= n; i++)
    {
        cin >> h[i];
    }
    int t = (n - r) / (r - l) + 1;
    if ((n - r) % (r - l) != 0)
    {
        t++;
    }
    int sum = (t - 1) * (r + l) + (n - (t - 1) * (r - l));
    //printf("%d %d\n", t, sum);
    int cur = 0;
    for (int i = 1; i <= n; i++)
    {
        if(h[i]%2==0)
        {
            h[i]--;
        }
        cur += min(h[i], 2 * (t - 1) + 1);
    }
    //printf("\n");
    if (cur < sum)
    {
        printf("No\n");
    }
    else
    {
        printf("Yes\n");
    }
    return 0;
}

B. Crash Test

Problem

初始时汽车离墙的距离为 D D D,同时给定 n n n 种前进距离 h i h_i hi。如果当前离墙的距离 r < h i r<h_i r<hi,那么汽车碰到墙后会反弹并继续移动 h i − r h_i-r hir 的距离,当 r ≥ h i r\ge h_i rhi 时, r = r − h i r=r-h_i r=rhi

问离墙的最近距离是多少?

数据范围: 1 ≤ n ≤ 100 , 1 ≤ D , h i ≤ 1 0 18 1\le n\le 100,1\le D,h_i\le 10^{18} 1n100,1D,hi1018

Solution

n = 1 n=1 n=1 时,答案显然为 m i n ( D   m o d   h 1 , h 1 − D   m o d   h 1 ) min(D\ mod\ h_1,h1-D\ mod\ h_1) min(D mod h1,h1D mod h1)

n = 2 n=2 n=2 时,由裴蜀定理可知存在整数 x , y x,y x,y,使得 x h 1 + y h 2 = g c d ( h 1 , h 2 ) xh_1+yh_2=gcd(h_1,h_2) xh1+yh2=gcd(h1,h2),假设汽车碰到墙后能继续前进,那么之后在墙另一边执行的操作就可以看作是负数。记 d = g c d ( h 1 , h 2 ) d=gcd(h_1,h_2) d=gcd(h1,h2),那么此时答案即为 m i n ( D   m o d   d , d − D   m o d   d ) min(D\ mod\ d,d-D\ mod\ d) min(D mod d,dD mod d)

n > 2 n>2 n>2 时,记 d = g c d ( h 1 , h 2 , …   ) d=gcd(h_1,h_2,\dots) d=gcd(h1,h2,),由数学归纳法可知,此时最小的距离为 m i n ( D   m o d   d , d − D   m o d   d ) min(D\ mod\ d,d-D\ mod\ d) min(D mod d,dD mod d)

时间复杂度: O ( n   l o g ( d ) ) O(n\ log(d)) O(n log(d)),其中 l o g ( d ) log(d) log(d) 为求 g c d gcd gcd 的复杂度。

Code

#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <cmath>
#define ll long long
using namespace std;
const int N = 5e5;
int n;
ll D,h[N+5];
int main()
{
    cin>>n>>D;
    for(int i=1;i<=n;i++)
    {
        cin>>h[i];
    }
    ll d=0;
    for(int i=1;i<=n;i++)
    {
        d=__gcd(d,h[i]);
    }
    printf("%lld\n",min(D%d,d-D%d));
    return 0;
}

D. Dominoes!

Problem

给定 n n n 块多米诺骨牌,每个多米诺骨牌有两个数字 x i x_i xi y i y_i yi,问怎么将这 n n n 块多米诺骨牌拼接成一条直线,使得相邻两块多米诺骨牌之间的数字均不同。

数据范围: 1 ≤ n ≤ 2 × 1 0 5 , 1 ≤ x i , y i ≤ 1 0 9 1\le n\le 2\times10^5,1\le x_i,y_i\le 10^9 1n2×105,1xi,yi109

Solution

首先想到可以将多米诺骨牌分成两部分,其中一部分 x i x_i xi y i y_i yi 相等,而另一部分 x i x_i xi y i y_i yi 不相等。对于不相等的部分显然可以拼接在一起,而相等的部分则要另外考虑。

对于 x i x_i xi y i y_i yi 相等的多米诺骨牌,每次挑选出现次数最多的两种不同的骨牌,将其拼接在一起,反复执行这一操作直到没有多余的骨牌或者只剩下一种骨牌。

接下去将剩余的相等的骨牌和不相等的骨牌轮流拼接在一起即可。如果当前可以拼接相等的骨牌就拼在一起,如果不可以就将不相等的骨牌拼接起来,直到不相等的骨牌被全部拼接完毕。

如果此时还剩下相等的骨牌,那么就说明不能将所有的骨牌拼接起来。

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

Code

#include <bits/stdc++.h>
using namespace std;
const int N = 5e5;
int n;
vector<pair<int, int>> ans, p;
map<int, int> mp;
priority_queue<pair<int, int>> q;
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        int x, y;
        cin >> x >> y;
        if (x == y)
        {
            mp[x]++;
        }
        else
        {
            p.push_back({x, y});
        }
    }
    for (auto it : mp)
    {
        q.push({it.second, it.first});
    }
    while (q.size() >= 2)
    {
        pair<int, int> p1, p2;
        p1 = q.top(), q.pop();
        p2 = q.top(), q.pop();
        ans.push_back({p1.second, p1.second});
        ans.push_back({p2.second, p2.second});
        p1.first--;
        p2.first--;
        if (p1.first)
        {
            q.push(p1);
        }
        if (p2.first)
        {
            q.push(p2);
        }
    }
    int num = 0, cnt = 0;
    if (q.size())
    {
        num = q.top().second;
        cnt = q.top().first-1;
        ans.push_back({num,num});
    }
    //printf("%d %d\n", num, cnt);
    for (int i = 0; i < p.size(); i++)
    {
        if (!ans.empty() && ans.back().second != num && cnt)
        {
            ans.push_back({num, num});
            cnt--;
        }
        if (!ans.empty() && ans.back().second == p[i].first)
        {
            swap(p[i].first, p[i].second);
        }
        ans.push_back(p[i]);
    }
    if (ans.size() != n)
    {
        printf("No\n");
        return 0;
    }
    printf("Yes\n");
    for (int i = 0; i < n; i++)
    {
        printf("%d %d\n", ans[i].first, ans[i].second);
    }
    return 0;
}

E. Malfunctioning Typewriter

Problem

给定 n n n 个长度为 m m m 的01串,这 n n n 个01串互不相同,定义好诗是将这 n n n 个01串以任何顺序拼接在一起形成的字符串。

现在又一台打字机,当你决定打字符 x x x 时,有 p p p 的概率可以正确打出,同时有 1 − p 1-p 1p 的概率打出相反的字符 1 − x 1-x 1x

问打出好诗的最大概率是多少。

数据范围: 1 ≤ n , m ≤ 1000 , 0.5 ≤ p < 1 1\le n,m\le1000, 0.5\le p<1 1n,m1000,0.5p<1

Solution

先对这 n n n 个01串建立字典树,这样题目就相当于要求在这颗字典树上走恰好 n n n,同时每次都不能走出字典树的最大概率。

对于字典树的某个节点 i i i,记 f i , 0 / 1 f_{i,0/1} fi,0/1 分别表示左右两边字符串出现的次数。要想使得概率最大,相当于在这个节点向左恰好 f i , 0 f_{i,0} fi,0 次以及向右恰好 f i , 1 f_{i,1} fi,1 次的概率要最大。同时个节点间是相互独立的,那么答案就是每个节点最大概率的乘积。

每个节点的概率可以预处理出来,记 g i , j g_{i,j} gi,j 表示向左有 i i i 个,向右有 j j j 个字符串的最大概率,那么

g i , j = m a x ( p × g i − 1 , j + ( 1 − p ) × g i , j − 1 , p × g i , j − 1 + ( 1 − p ) × g i − 1 , j ) g_{i,j}=max(p\times g_{i-1,j}+(1-p)\times g_{i,j-1},p\times g_{i,j-1}+(1-p)\times g_{i-1,j}) gi,j=max(p×gi1,j+(1p)×gi,j1,p×gi,j1+(1p)×gi1,j)

解释:对于当前的字符可以选择打字符0,此时有 p p p 的概率打印成功,状态变成 g i − 1 , j g_{i-1,j} gi1,j,有 1 − p 1-p 1p 的概率打印失败,打出的字符变成1,此时状态变成 g i , j − 1 g_{i,j-1} gi,j1,选择打字符1同理,然后两种情况取 m a x max max 即可。

最后的答案为: Π g f i , 0 , f i , 1 \Pi g_{f_{i,0},f_{i,1}} Πgfi,0,fi,1

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

Code

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e6;
int n, m, cnt;
int nxt[N + 5][2], f[N + 5][2];
double g[1005][1005], p;
void insert(string s)
{
    int p = 0;
    for (int i = 0; i < (int)s.size(); i++)
    {
        int c = s[i] - '0';
        if (!nxt[p][c])
        {
            nxt[p][c] = ++cnt;
        }
        f[p][c]++;
        p = nxt[p][c];
    }
}
int main()
{
    cin >> n >> m >> p;
    for (int i = 1; i <= n; i++)
    {
        string s;
        cin >> s;
        insert(s);
    }
    g[0][0] = 1;
    for (int i = 1; i <= n; i++)
    {
        g[i][0] = g[i - 1][0] * p;
        g[0][i] = g[0][i - 1] * p;
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            g[i][j] = max(p * g[i - 1][j] + (1 - p) * g[i][j - 1], p * g[i][j - 1] + (1 - p) * g[i - 1][j]);
            // printf("%lf ", g[i][j]);
        }
        // printf("\n");
    }
    double ans = 1;
    for (int i = 0; i <= cnt; i++)
    {
        //printf("%d %d\n",f[i][0],f[i][1]);
        ans = ans * g[f[i][0]][f[i][1]];
    }
    printf("%.15lf\n", ans);
    return 0;
}

J. Rigged Games

Problem

两支队伍打比赛,其中大分为 2 b − 1 2b-1 2b1,小分为 2 a − 1 2a-1 2a1。同时给定长度为 n n n 的01串,比赛的结果将由这个字符串无限重复得到。

问从第 i   ( 1 ≤ i ≤ n ) i\ (1\le i\le n) i (1in) 个字符开始,获胜的队伍是哪支?

数据范围: 1 ≤ n , a , b ≤ 1 0 5 1\le n,a,b\le10^5 1n,a,b105

Solution

对于每个 i i i 来说,要找到第一次出现某支队伍大分为 b b b 的位置。可以发现队伍的得分是递增的,因此我们可以通过倍增的方式快速的找到这一位置。

f i , j f_{i,j} fi,j 表示从 i i i 开始经过 2 j 2^j 2j 次大分后的比赛情况,包括两支队伍的得分 x x x y y y,以及比赛结束的位置 p o s pos pos,可以用结构体将这些信息放在一起便于处理。

初始值 f i , 0 f_{i,0} fi,0 可以通过双指针的方式在一次遍历中得到,其他位置通过下面的公式得到:
f i , j = f f i , j − 1 , j − 1 f_{i,j}=f_{f_{i,j-1},j-1} fi,j=ffi,j1,j1

最后倍增找到大比分恰好为 b b b 的位置即可。

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

Code

#include <bits/stdc++.h>
using namespace std;
const int N = 5e5;
int n, a, b;
string t;
struct node
{
    int x, y;
    int pos;
} f[N + 5][20];
int main()
{

    cin >> n >> a >> b;
    cin >> t;
    int cnt0 = (t[0] == '0'), cnt1 = (t[0] == '1');
    for (int i = 0, j = 0; i < n;)
    {
        if (cnt0 == a || cnt1 == a)
        {
            f[i][0].x = (cnt0 == a);
            f[i][0].y = (cnt1 == a);
            f[i][0].pos = j;
            // printf("%d %d %d\n", f[i][0].x, f[i][0].y, f[i][0].pos);
            if (t[i] == '0')
            {
                cnt0--;
            }
            else
            {
                cnt1--;
            }
            i++;
        }
        else
        {
            j = (j + 1) % n;
            if (t[j] == '0')
            {
                cnt0++;
            }
            else
            {
                cnt1++;
            }
        }
    }
    for (int j = 1; j < 20; j++)
    {
        // printf("j=%d\n", j);
        for (int i = 0; i < n; i++)
        {
            f[i][j].x = f[i][j - 1].x + f[(f[i][j - 1].pos + 1) % n][j - 1].x;
            f[i][j].y = f[i][j - 1].y + f[(f[i][j - 1].pos + 1) % n][j - 1].y;
            f[i][j].pos = f[(f[i][j - 1].pos + 1) % n][j - 1].pos;
            // printf("%d %d %d\n", f[i][j].x, f[i][j].y, f[i][j].pos);
        }
    }
    for (int i = 0; i < n; i++)
    {
        int cur = i;
        int cnt0 = 0, cnt1 = 0;
        for (int j = 19; j >= 0; j--)
        {
            if (f[cur][j].x + cnt0 >= b || f[cur][j].y + cnt1 >= b)
            {
                continue;
            }
            cnt0 += f[cur][j].x;
            cnt1 += f[cur][j].y;
            cur = (f[cur][j].pos + 1) % n;
            // printf("%d %d\n", cur, j);
        }
        cnt0 += f[cur][0].x;
        cnt1 += f[cur][0].y;
        // printf("%d %d \n", cnt0, cnt1);
        if (cnt0 == b)
        {
            printf("0");
        }
        else
        {
            printf("1");
        }
        // printf("\n");
    }
    printf("\n");
    return 0;
}

L. Sudoku and Minesweeper

Problem

给定一个大小 9 × 9 9\times 9 9×9 的数独,问能否将其中某些数字替换成炸弹,使得数字表示的是周围炸弹的数量。同时要求不能将所有的数字替换成炸弹。

Solution

因为给的是数独的形式,所以必定存在一个数字 8 8 8 在中间,那么只要将除这个位置外的所有数字全部替换成炸弹即可。

Code

#include<bits/stdc++.h>
using namespace std;
string s[9];
int ans[9][9];
void solve()
{
    for(int i=1;i<8;i++)
    {
        for(int j=1;j<8;j++)
        {
            if(s[i][j]=='8')
            {
                ans[i][j]=1;
                return;
            }
        }
    }
}
int main()
{
    for(int i=0;i<9;i++)
    {
        cin>>s[i];    
    }
    solve();
    for(int i=0;i<9;i++)
    {
        for(int j=0;j<9;j++)
        {
            if(ans[i][j])
            {
                printf("8");
            }
            else{
                printf("*");
            }
        }
        printf("\n");
    }
    return 0;
}
  • 25
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值