牛客周赛 Round 68 B~D

从本篇文章开始,该专栏改为发布蓝桥杯、牛客、洛谷的周赛或小白赛的B到D题讲解,因为A是很容易的签到题,不作讲解,D以后的题对于我来说有些难度,也不做讲解。

先看B题B题传送门icon-default.png?t=O83Ahttps://ac.nowcoder.com/acm/contest/95928/B

标签 :字符转化、一/二维前缀和

B题是常见的一种思考转化策略,我一开始没想到,想的就是直接暴力的去枚举起始下标和终点下标,然后再去遍历看是否含有蘑菇,其实这道题是很经典的变形,因为数据范围达到1e5的缘故暴力去枚举和遍历会t掉,正确做法是由于棋盘中只有蘑菇和空地两种标识字符,于是我们把要求的空地字符变成数字0,要避免的蘑菇变为数字1,这样我们可以用二位前缀和来存储棋盘情况,如果我们枚举的当前空间中面积和为0,说明全是空地,如果大于0说明有蘑菇的存在,用这种优化成前缀和的方法,即可省去中间重复遍历的时间消耗。

如果是要求一块面积中要求某元素和某元素出现次数相等,要求最大面积为多大,我们可以把一个符号变成1另一个变成-1,求面积前缀和为0即可。

本题代码如下

#include <iostream>
using namespace std;
const int N = 35;
int n, m;
char a[N][N];
int s[N][N];
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; ++ i )
        for (int j = 1; j <= m; ++ j )
        {
            cin >> a[i][j];
            s[i][j] = a[i][j] == '*';
        }
    for (int i = 1; i <= n; ++ i )
        for (int j = 1; j <= m; ++ j )
            s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
    int sum = -1, l1, r1, l2, r2;
    for (int x1 = 1; x1 <= n;++ x1 )
        for (int y1 = 1; y1 <= m; ++ y1 )
            for (int x2 = 1; x2 <= n; ++ x2 )
                for (int y2 = 1; y2 <= m; ++ y2 )
                    if (s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1] == 0)
                    {
                        int t = (x2 - x1 + 1) * (y2 - y1 + 1);
                        if (t > sum)
                        {
                            sum = t;
                            l1 = x1, r1 = y1, l2 = x2, r2 = y2;
                        }
                    }
    printf("%d %d %d %d\n", l1, r1, l2, r2);
    return 0;
}

就是转化和前缀和构造模板,再然后是枚举起始坐标和终点坐标,然后前缀和判断是否为0


C题传送门icon-default.png?t=O83Ahttps://ac.nowcoder.com/acm/contest/95928/C标签:数学、思维题

C题一开始读错题意了,所以感觉wa的莫名其妙,只考虑到了不同区间的降重,而没看到题目要求同一个区间也不能有重复的数字。正解是先考虑同数组直接的降重,同数组降重的操作是一定要做的,思路是统计出两个数组分别要进行自己数组降重多少次,由于题目中说两个数组每回合可以一起删掉一个数字,所以两个数组总共需要因为自降重花费的时间为max第一个数组自降重次数和第二个数组自降重次数。

说完这个我们再看两个数组之间的降重,首先我们要考虑把两个数组有多少个重复数字统计出来,然后看由于两个数组自降重导致的多出来时间可以抵消一部分两数组间去重,意思就是假设第一个数组自降重时间要长一些,所以第二个数组会提早结束自降重阶段,而这一段等待第一个数组自降重的时间第二个数组还可以做一些去重的操作,来抵消一部分甚至是全部的两数组间降重的时间。因为这段时间第二个数组可以删除自己一些元素,让它和第一个数组间没有重复数字。

算完抵消后的时间我们还要整体除2,因为我们知道两个数组可以同时删掉一个元素,第一个数组删掉一个重复的,第二个数组再删掉一个,这样删除就可以尽力缩短所用时间。

#include <iostream>
#include <map>
using namespace std;
int n;

map<int, int> cnt[2];
int main()
{
    cin >> n;
    for (int i = 0; i < 2; ++ i )
        for (int j = 0; j < n; ++ j ) 
        {
            int x; cin >> x;
            cnt[i][x] ++;
        }
    int sum[2] = {0};
    for (int i = 0; i < 2; ++ i )
        for (auto& t : cnt[i]){
            if (t.second > 1)
            {
                sum[i] += t.second - 1;
            }
            t.second = 1;
        }
    int ans = max(sum[0], sum[1]);
    int x = 0;
    for (auto t : cnt[0])
        if (cnt[1].count(t.first)) x ++;
    x = max(0, (x - abs(sum[0] - sum[1]) + 1) / 2);
    ans += x;
    cout << ans;
  return 0;
}

这道题的难点和关键在于如何分析出来所用时间最短,而不能在没自去重的情况下,去考虑数组间去重,这样会使情况变得十分复杂。

代码解释就是先进行两个数组的数据读取,用map来存储每个数字在不同数组间出现多少次,然后进行自去重,但是要记得保存1个,因为这样可以不用花费那么多时间也符合规定,用ans来保存答案,然后再遍历一下两个数组,进行数组间去重,这里用map除了起到哈希表的作用以外,另一个非常重要的作用在于map使用红黑树使得查询操作count时间为logn,否则进行1e5双for查询容易te。最后去看能不能抵消一部分时间,再除上2,注意不要减反了。。


D题传送门icon-default.png?t=O83Ahttps://ac.nowcoder.com/acm/contest/95928/D标签:数学、思维、分解质因子、前后缀数组、二进制

D题我觉得已经是我目前能补的极限了,感觉这种思维题,像CD这样的比赛中根本想不出来,最多能给B写出来就已经很好了。。。

这道题的题意是最多操作一次然后求出ai*aj(i<j)的乘积是495的倍数,看数据就知道这道题是非暴力题,看到要求某个数或某些数字是某一个数字的倍数这样的题,通常要联想到分解质因子,我们将495分解质因子,变成3*3*5*11。

题解转化为要求两个非逆序的数字乘积,且该乘积包含至少两个3一个5一个11,也就是说如果是两个数字分别对3、9、5、11取模,这两个数字其中之一各占这些数的其中几个,且两个数字的组合刚好构成这些数字至少一次,那么我们说这两个数字恰好符合乘积是495的倍数。

做法是将输入进来的数字分别进行对3、9、5、11取模,然后将取模结果返回,应该为0——15的数字,将该数字作为评判标准,开一数组preij代表前i-1数字中,经过取模后结果为j的数字共有多少个,将每个数字的取模数字存到数组ai中,然后进行判断,看取模结果为0——15的数字与当前数字的取模结果,是否可以组成两个三一个五一个十一的结果,判断具体为j和ai或操作等于15,或者j和ai或操作等于13,且j有一个3,ai有一个3,第一个判断就是其中一个有9,而第二个是两个都含有一个3。

部分代码如下

#include <iostream>
#include <vector>
using namespace std;
const int N = 4e5 + 10;
int a[N], val[N], n;

int calc(int x)
{
    int sum = 0;
    if (x % 3 == 0 ) sum |= 1;
    if (x % 9 == 0 ) sum |= 2;
    if (x % 5 == 0 ) sum |= 4;
    if (x % 11 == 0 ) sum |= 8;
    return sum;
}
bool check (int j, int x)
{
    return (j | x) == 15 || ((j | x) == 13 && j & 1 && x & 1);
}
int main()
{
    cin >> n;    
    long long ans = 0;
    vector<vector<int>> pre(n + 2, vector<int> (20));
    vector<vector<int>> suf(n + 2, vector<int> (20));
    for (int i = 1; i <= n; ++ i )
    {
        int x; cin >> x;
        val[i] = x;
        a[i] = calc(x);
        for (int j = 0; j < 16; ++ j ) if (check(j, a[i])) ans += pre[i - 1][j];
        pre[i] = pre[i - 1];
        pre[i][a[i]] ++;
    }

pre数组的更新是首先继承前一个位置也就是前i-1的数字答案,然后再让本次的二进制ai进行自增一,判断部分也说过了,就是如果j和当前ai数字的二进制能相匹配,那么就是加上pre存的对应的符合互补二进制数字的数字有多少。

再看suf数组的作用,它是存储后缀数组的,因为我们不知道哪个数字要进行+1,我们要实验每个数字+1最终对答案的贡献,也即是,模拟每个数字加1,而这个数字经过加1后对整个数组的贡献为加1后对其前面的数字的贡献+对其后面数字的贡献再减去加1之前对其前后的贡献,这是因为我们没加1之前的计算都写在pre和suf里面了,如果不想加1后再减去我认为重新写一个新的存储加1后的也是可以的。。但是空间可能会超。。

以下为suf求解代码

for (int i = n; i >= 1; -- i )
    {
        suf[i] = suf[i + 1];
        suf[i][a[i]] ++;
    }

然后是求加1后的贡献

int maxa = 0;
    for (int i = 1; i <= n; ++ i )
    {
        int x = val[i] + 1;
        int t = 0, mask = calc(x);
        for (int j = 0; j < 16; ++ j ) if (check(j, mask)) t += pre[i - 1][j];
        for (int j = 0; j < 16; ++ j ) if (check(j, mask)) t += suf[i + 1][j];
        
        for (int j = 0; j < 16; ++ j ) if (check(j, a[i])) t -= pre[i - 1][j];
        for (int j = 0; j < 16; ++ j ) if (check(j, a[i])) t -= suf[i + 1][j];
        maxa = max(maxa, t);
    }

四个循环应该可以被优化为两个,分开写只是为了更好的理解。mask求出的是加1后的二进制,然后判断匹配时候,因为其他数字还是没加1的所以还是可以接着用,因为最多操作一次,加上前后贡献,然后减掉加1前的对前后造成的贡献,这里可能很多读者不理解为什么还要减掉之前没加1的,因为我们这个pre和suf也就是前后缀数组就是根据没加1的来计算的,虽然t累加的是加1后check成功的结果值,但是数组内容本质上还是存有加1之前i-1数字为止,二进制为j的数字个数,所以要剪掉,不然答案是错误的。

最后和ans累加起来,即是答案,ans是没操作过的,maxa存储的是最多操作一次后的将它们都加在一起就是要求的答案,比如样例,没操作之前是1,操作之后是3,加一起才是4,这里的题目我认为没太说清,是累加在一起的,但是最后的答案很明显就是累加在一起,题目有点歧义。

最后的完整代码如下

#include <iostream>
#include <vector>
using namespace std;
const int N = 4e5 + 10;
int a[N], val[N], n;

int calc(int x)
{
    int sum = 0;
    if (x % 3 == 0 ) sum |= 1;
    if (x % 9 == 0 ) sum |= 2;
    if (x % 5 == 0 ) sum |= 4;
    if (x % 11 == 0 ) sum |= 8;
    return sum;
}
bool check (int j, int x)
{
    return (j | x) == 15 || ((j | x) == 13 && j & 1 && x & 1);
}
int main()
{
    cin >> n;    
    long long ans = 0;
    vector<vector<int>> pre(n + 2, vector<int> (20));
    vector<vector<int>> suf(n + 2, vector<int> (20));
    for (int i = 1; i <= n; ++ i )
    {
        int x; cin >> x;
        val[i] = x;
        a[i] = calc(x);
        for (int j = 0; j < 16; ++ j ) if (check(j, a[i])) ans += pre[i - 1][j];
        pre[i] = pre[i - 1];
        pre[i][a[i]] ++;
    }
    //cout << ans;
    for (int i = n; i >= 1; -- i )
    {
        suf[i] = suf[i + 1];
        suf[i][a[i]] ++;
    }
    int maxa = 0;
    for (int i = 1; i <= n; ++ i )
    {
        int x = val[i] + 1;
        int t = 0, mask = calc(x);
        for (int j = 0; j < 16; ++ j ) if (check(j, mask)) t += pre[i - 1][j];
        for (int j = 0; j < 16; ++ j ) if (check(j, mask)) t += suf[i + 1][j];
        
        for (int j = 0; j < 16; ++ j ) if (check(j, a[i])) t -= pre[i - 1][j];
        for (int j = 0; j < 16; ++ j ) if (check(j, a[i])) t -= suf[i + 1][j];
        maxa = max(maxa, t);
    }
    cout << ans + maxa;
    return 0;
}

制作不易,有用请三连支持蟹蟹。

关于Round 83 的具体题目和解答,目前并未提供直接的引用支持。然而,可以基于以往的经验以及类似的周赛模式来推测可能涉及的内容结构。 通常情况下,周赛会包含多个不同难度级别的题目,从简单的签到题(A 类型)到较难的挑战性问题(E 或 F 类型)。以下是根据已有经验构建的一般框架: ### 周赛 Round 83 可能的主题 #### A - 签到题 这类题目通常是简单算法的应用或者基础逻辑判断。例如: ```python def solve_a(): n = int(input()) result = sum(range(1, n + 1)) # 计算前N项自然数之和 print(result) solve_a() ``` 此部分无需深入解析,主要考察参赛者的基础编程能力[^1]。 #### B - 中等难度题 此类题目可能会涉及到数组操作、字符串处理或基本数据结构应用。比如给定一段文字统计特定字符频率的问题。 ```python from collections import Counter def solve_b(): s = input().strip() counter = Counter(s) most_common_char, count = counter.most_common(1)[0] print(most_common_char, count) solve_b() ``` 上述代码片段展示了如何利用Python内置库快速解决常见计数类问题[^2]。 #### C/D/E/F 更高阶挑战 这些更复杂的任务往往需要运用高级技巧如动态规划(DP),图论(Graph Theory)或者其他专门领域知识才能有效完成。由于缺乏具体的Round 83资料,这里仅给出一个假设性的例子有关最短路径寻找: ```python import heapq INF = float('inf') def dijkstra(graph, start_node): distances = {node: INF for node in graph} distances[start_node] = 0 priority_queue = [(0, start_node)] while priority_queue: current_distance, current_vertex = heapq.heappop(priority_queue) if current_distance > distances[current_vertex]: continue for neighbor, weight in graph[current_vertex].items(): distance = current_distance + weight if distance < distances[neighbor]: distances[neighbor] = distance heapq.heappush(priority_queue, (distance, neighbor)) return distances graph_example = { 'A': {'B': 1, 'C': 4}, 'B': {'A': 1, 'C': 2, 'D': 5}, 'C': {'A': 4, 'B': 2, 'D': 1}, 'D': {'B': 5, 'C': 1} } print(dijkstra(graph_example, 'A')) ``` 这段程序实现了经典的迪杰斯特拉算法用于求解加权无向图中的单源最短路径问题[^3]. 尽管无法确切知道每道实际考题是什么样子,但通过以上介绍应该能够帮助理解一般竞赛形式下的潜在考点及其解决方案设计方法.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学习算法的杨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值