从本篇文章开始,该专栏改为发布蓝桥杯、牛客、洛谷的周赛或小白赛的B到D题讲解,因为A是很容易的签到题,不作讲解,D以后的题对于我来说有些难度,也不做讲解。
先看B题B题传送门https://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题传送门https://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题传送门https://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;
}
制作不易,有用请三连支持蟹蟹。