2024牛客多校题解3(A,B,J,L)

A Bridging the Gap

题意,有n个人要划船到对岸,每次船上最少l个人,最多r个人,每次坐船船上的每个人都需要消耗一点体力,每个人的体力各不相同(由输入给出),问这种情况下能不能把所有人送到对岸

思路:已知任何一个人要到对岸至少需要消耗1点体力,那么我们不妨假设所有人都已经到了对岸,让所有人的体力都-1,这样就不用考虑体力为1的人的贡献,然后考虑能够往返两岸带人的人的贡献,即减少1后的体力除以2,因为一次往返至少消耗2点体力,不满2点体力就回不来了,所以有效的只是2点体力的倍数,这样我们就知道每个人可以往返几次,而往返一次可以带(r-l)人过来,最后一次不用回去就相当于带了r个人,那么我们不如让往返总次数尽可能多。于是我们考虑二分答案的解法,二分能跑的趟数,写一个check函数检查这么多趟数可以往返吗,可以就往大了继续算,否则往小了找即可。

代码

#include<iostream>
#include<string>
#include<cstring>
#include<map>
#include<queue>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
#define ll long long

const ll N = 1e5 + 10;

ll arr[5 * N + 10] = { 0 };
ll tim[5 * N + 10] = { 0 };

ll check(ll tol,ll n,ll l)
{
	ll cnt = 0;
	for (int i = 1; i <= n; i++)
	{
		if (tim[i] > tol)
		{
			cnt += tol;
		}
		else
		{
			cnt += tim[i];
		}
	}
	if (cnt >= l*tol) return 1;
	return 0;
}

void solve()
{
	ll n, l, r;
	cin >> n >> l >> r;
	for (int i = 1; i <= n; i++)
	{
		cin >> arr[i];
		tim[i] = (arr[i] - 1) / 2;
	}
	ll low = 0;
	ll high = 1e10;
	ll ans = 0;
	while (low <= high)
	{
		ll mid = (low + high) / 2;
		if (check(mid, n, l)) low = mid + 1,ans=max(ans,mid);
		high = mid - 1;
	}
	if ((r - l) * ans >= n - r)
	{
		cout << "Yes" << endl;
	}
	else
	{
		cout << "No" << endl;
	}
}


signed main()
{
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	ll t;
	t = 1;
	while (t--)
	{
		solve();
	}
}

B Crash Test

题意:给出n个移动距离以及一开始的总距离,可以任意次操作选取n个距离中的某一个使得车向墙壁移动这么多距离,如果移动x后会撞到墙壁,那就会反弹回来d-x的距离,问如何操作使得最后和墙壁距离最小,最小值是多少。

思路:求所有可移动距离的gcd,可以证得一定可以通过多种距离的操作使得这个移动距离能被组合出来,最后看看是d%val小还是val-d%val小即可

实现比较简单就不贴代码了

J Rigged Games

题意,给出一个只有1,0组成的长度为n的字符串,1代表1赢,0代表0赢。给出a,b两个数字,a表示小局赢多少为赢,如a为3表示谁先赢三小局则算赢1大局。b表示大局赢多少算赢。

问从字符串第i个位置开始算,不够的话进行循环,这个位置开始算1和0谁会赢,每个位置对应一个答案

思路暴力跑时间复杂度会到n的三次明显是不可行的,那我们就考虑该如何去优化这个暴力的思路,首先是最内部一层对小局的枚举,我们发现,采用前缀和的思路可以快速找出一个区域里面谁赢的更多,又发现因为是循环的同一个数组,所以在某个位置结束后,从下一个位置开始算的下一段到哪里结束这个肯定也是固定的,因此我们可以通过前缀和以及二分去处理从第i个位置开始到哪里结束,从这里开始的输赢又是如何。

这样化简后可以得到n方logn的复杂度,可以发现还是会超时的,因此我们对大局的枚举也需要化简,我们会发现,处理好小局之后,我们已经知道从哪里开始谁赢且跳跃到哪里,那么这样跳最多2b-1次就肯定会找到大局是谁赢,因为最多交错赢肯定有一个先到b。但是每个位置去跳2b-1次又很浪费时间,有没有办法缩减跳的次数呢?这里我们就会想到倍增跳法。

倍增思想学习链接:洛谷LCA P3379

通过预处理倍增我们就可以2的n次这样跳,很快就可以跳满2b-1次,又缩减了时间复杂度,这样就可以通过此题了。

代码

#include<iostream>
#include<string>
#include<cstring>
#include<map>
#include<queue>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
#define ll long long

const ll N = 1e5 + 10;

struct Node {
    int l, r;
};

int n, m, k, t;
ll arr[N], cnt[N];
string s;
int a, b;
int S1[N], S0[N];
int f[N][30], g[N][30];

int check(int x, int p, int s) {
    if (s == 1) return (S1[x] - S1[p - 1]);
    return (S0[x] - S0[p - 1]);
}

int find(int p, int s, int A) {
    int l = p, r = n;
    while (l < r) {
        int mid = l + r >> 1;
        if (check(mid, p, s) >= A) r = mid;
        else l = mid + 1;
    }
    return r;
}

signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);

    cin >> n;
    cin >> a;
    cin >> b;
    cin >> s;
    s = ' ' + s;
    for (int i = 1; i <= n; i++) {
        S0[i] = S0[i - 1] + (s[i] == '0');
        S1[i] = S1[i - 1] + (s[i] == '1');
    }

    if (S0[n] == 0) {
        for (int i = 1; i <= n; i++) {
            cout << 1;
        }
        cout << "\n";
        return 0;
    }
    if (S1[n] == 0) {
        for (int i = 1; i <= n; i++) {
            cout << 0;
        }
        cout << "\n";
        return 0;
    }

    for (int i = 1; i <= n; i++) {
        int cnt = 1;
        int p = i, cnt1 = 0, cnt0 = 0;
        int res1 = S1[n] - S1[p - 1], res0 = S0[n] - S0[p - 1];
        if (res1 >= a || res0 >= a) {

            int l1 = p, r1 = n;
            while (l1 < r1) {
                int mid = l1 + r1 >> 1;
                if (check(mid, p, 1) >= a) r1 = mid;
                else l1 = mid + 1;
            }
            if (check(r1, p, 1) < a) r1 = 1e9;
            int l0 = p, r0 = n;
            while (l0 < r0) {
                int mid = l0 + r0 >> 1;
                if (check(mid, p, 0) >= a) r0 = mid;
                else l0 = mid + 1;
            }
            if (check(r0, p, 0) < a) r0 = 1e9;
            if (r1 <= r0) cnt1++, p = r1 % n + 1;
            else cnt0++, p = r0 % n + 1;
        }
        else {

            res1 = a - res1, res0 = a - res0;
            int t1 = (res1 - 1) / S1[n], t0 = (res0 - 1) / S0[n];
            if (t1 > t0) {
                res0 = res0 % S0[n];
                if (res1 == 0) res1 = S1[n];
                if (res0 == 0) res0 = S0[n];
                int l0 = 1, r0 = n;
                while (l0 < r0) {
                    int mid = l0 + r0 >> 1;
                    if (check(mid, 1, 0) >= res0) r0 = mid;
                    else l0 = mid + 1;
                }
                p = r0 % n + 1;
                cnt0++;
            }
            else if (t1 < t0) {
                res1 = res1 % S1[n];
                if (res1 == 0) res1 = S1[n];
                if (res0 == 0) res0 = S0[n];
                int r = find(1, 1, res1);
                p = r % n + 1;
                cnt1++;
            }
            else {
                res1 = res1 % S1[n]; res0 = res0 % S0[n];
                if (res1 == 0) res1 = S1[n];
                if (res0 == 0) res0 = S0[n];
                int r1 = find(1, 1, res1);
                int r0 = find(1, 0, res0);
                if (r1 <= r0) cnt1++, p = r1 % n + 1;
                else cnt0++, p = r0 % n + 1;
            }
        }

        cnt++;
        if (cnt1) f[i][0] = 1; //记录第i个位置是谁小局赢了
        g[i][0] = p;//记录这位置小局赢了后从哪个位置开始
    }

    for (int k = 1; k < 30; k++)
    {
        for (int i = 1; i < s.size(); i++)
        {
            g[i][k] = g[g[i][k - 1]][k - 1]; //倍增跳法转移方程,g[i][k]表示的从i位置开始是赢2的k次局后跳的位置,就是先跳2的k-1次再跳2的k-1次即可
            f[i][k] = f[i][k - 1] + f[g[i][k - 1]][k - 1]; //倍增赢局数转移方程,f[i][k]表示从i位置开始赢2的k次局后的值,这个的转移明显就是从i位置先赢2的k-1次局+赢2的k-1次局后的位置再赢2的k-1次局后的值。
        }
    }

    string ans;
    for (int i = 1; i < s.size(); i++) //枚举从i开始跑谁会先赢大局
    {
        int val = 2 * b - 1; //最多跑这么多次必定有一个胜出
        int p = i;
        int sum = 0;
        for (int k = 29; k >= 0; k--) //倍增跳法计算赢了多少次
        {
            if (val >= ((ll)1 << k))
            {
                sum += f[p][k];
                p = g[p][k]; 
                val -= ((ll)1 << k);
            }
        }
        if (sum >= b)
        {
            ans += '1'; //根据赢的小局数判断谁赢。
        }
        else
        {
            ans += '0';
        }
    }
    cout << ans << endl;
}

L Sudoku and Minesweeper

题意:给出一个99的矩阵,本质上是由9个33的数独构成的,每个位置表示周围有几个雷,让你输出一个9*9的矩阵,把可能的情况标出来,但是不能是全都是累的矩阵

思路:本场最简单的签到题,因为输出要求只要不是全雷就行,那我们只要找到一个地方放数字就行,很显然找8是最优的,这样只要把周围全放雷就行,且8不能是边框的8,不然周围不满8个雷,正好题目是由9个数独块构成,那么必然有不在边框上的8,找到这个8记录,最后输出的时候这个位置输出8,别的地方输出雷即可。

  • 12
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值