如何做网站框架南宁企业网站制作模板
目录
前言
最佳牛围栏
分析
代码
壁画
分析
代码
K倍区间
分析
代码
统计子矩阵
分析
代码
递增三元组
分析
代码
激光炮弹
分析
代码
总结
前言
虽迟但到,主播今天去打了一下蓝桥杯的月赛,只ac了三道,鉴定为省三QAQ。
今天为大家带来了一道二分的题目和三道前缀和的题目,话不多说我们马上开始吧。
最佳牛围栏

分析
看见牛就知道来自USACO,就知道题目不会很简单。
发现问题可以用遍历来解决,考虑一下能不能二分。
先来尝试二分围起来的田地数量,很显然不满足单调性。
随后我们来尝试二分答案,能否二分平均值然后判断能否满足条件呢?
我们假设存在最大平均值m。
很明显对于平均值小于等于m的情况皆可以满足,而对于平均值大于m的情况则不可以满足(这道题因为max是一种包含关系,所以可以用二分)。
所以我们通过二分来引入平均值m。
接下来只需要判断是否存在平均值大于等于m即可。
但若挨个计算区间的平均值的话很显然是会超时的,需要优化。
如何来优化呢?
第一步,我们先将平均值计算的一步优化:
对于计算平均值显然是不可以直接使用前缀和的,我们列出式子
S / n <= m
移项
S <= m * n
将S拆分
a1 + a2 + ... + an <= m + m + ... + m
每一项减去m
a1 - m + a2 - m + ... + an - m <= 0
因为每次的m都是确定的,属于离线问题,所以可以通过前缀和来优化。
第二步,对区间枚举进行优化:
假设我们选定区间s,根据题目要求s的长度是要大于等于k的,我们将他转换成前缀减前缀
sj - si,为方便大家理解我画了图出来:

因为max是一种包含关系,所以我们不需要将每个区间的确切值算出来比较,只需要判断大小便好。
借助动态规划的思想,我们取min(si),即所有区间长度小于等于i的所有前缀的最小值。随后判断sj - min(si)的正负便好。
代码
// 二分答案
#include<iostream>
using namespace std;
const int N = 100010;
const double INF = 1e-6;
int n, f;
int nums[N];
double s[N];bool func(double x)
{//cout << x << endl;for(int i = 1; i <= n; i++){s[i] = s[i - 1] + nums[i] - x;//cout << s[i] << ' ';}//puts("");double l = 0;for(int i = 1; i <= n - f + 1; i++){//cout << l << ' ';l = min(l, s[i - 1]); //寻找最小前缀if(s[i + f - 1] - l >= 0) //存在至少一种可能可以使得区间大于0return true;}//puts("");return false;
}int main()
{scanf("%d%d", &n, &f);for(int i = 1; i <= n; i++){scanf("%d", nums + i);}double l = 0, r = 2000;while(r - l > INF){double x = (l + r) / 2;if(func(x)) //可以满足l = x;else //不能满足r = x;}printf("%d", (int)(r * 1000));return 0;
}
壁画

分析
这其实是一道思维题。先来给大家叙述一遍题目
给n段连续的墙体。
t第一次可以任选一个地方画画,但是在之后只能选择与已经画了的地方的相邻位置画画(也就是一个连续的区间)。
每天结束后都会有一段墙面崩溃,且只会崩溃只与一段还未毁掉的墙面相邻的墙面(其实就是前缀和后缀)。
且崩溃无法蔓延到已经画过画的部分。
先来考虑极限情况,第一次在端点作画。
由于t是先手,所以这种情况t可以画画ceil(n/2)次。
接下来我们来思考一般情况,来分析是不是一定可以画到ceil(n/2)次(t是绝顶聪明的,只判断可行性。)
我们假设t画到了ceil(n/2)次,如图:

对两边画出沿交界点对称的区间。

第一次选择两个区间的中间进行画画,随后我们可以发现只要我们每次选择摧毁的那个方向画画,最后就一定能画满ceil(n/2)次。
所以我们只需要查找所有长度为ceil(n/2)的区间的最大值即可。
要进行离线的区间和操作,使用前缀和。
代码
// 毁坏程度与绘画天数相关联,枚举起始位置
#include<iostream>
using namespace std;
const int N = 5000010;
char a[N];
int s[N];
int t, n, l;int func(int x) //根据初始位置计算可行天数
{int l = (n + 1)/2;return s[x + l - 1] - s[x - 1];
}int main()
{scanf("%d", &t);for(int k = 1; k <= t; k++){scanf("%d%s", &n, a + 1);for(int i = 1; i <= n; i++)s[i] = s[i - 1] + a[i] - '0'; //前缀和int l = 0;for(int i = 1; i <= n / 2 + 1; i++) //枚举起点,找最大值即可l = max(l, func(i));printf("Case #%d: %d\n",k, l);}return 0;
}
K倍区间

分析
要求区间和,我们使用前缀和,可得
(Sj - Si) mod K == 0。
根据同余定理变形可得
Sj mod K == Si mod K。
所以我们只需要枚举Sj,每次记录与他同余的Si的数量便好。(mod == 0需要单独计算)
代码
// 暴力O(n ^ 2),
// S % k == 0, 将S拆分——(Sj - Si)% k == 0 <---> Sj % k = Si & k转化成同余问题
// 而题目要求连续的区间,转换成后缀减去前缀,所以直接打表即可
#include<iostream>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int a[N];
LL s[N];
int st[N];
int n, k;
int main()
{scanf("%d%d", &n, &k);for(int i = 1; i <= n; i++){scanf("%d", a + i);s[i] = s[i - 1] + a[i];}LL l = 0;for(int i = 1; i <= n; i++){int m = s[i] % k;if(m == 0) l++;l += st[m]++;}printf("%lld", l);return 0;
}
统计子矩阵

分析
直接枚举矩阵的话时间复杂度为O(n ^ 4),很显然会超时。
考虑优化,我们发现每个点的数值都是大于0的,即矩阵和会随矩阵的增大而增大,具有单调性。
但是在二维上利用单调性显然是非常复杂的,如何简化问题呢?
降维!我们将枚举两个点拆分为上下边界和左右边界。
我们先对每一列进行前缀和操作,随后每次先枚举上下边界。
问题转换为了区间求和问题,显然暴力枚举区间的话还是会超时,如何优化呢?
前面说了区间具有单调性,我们需要的只是满足条件的那一部分,那么有没有一种算法可以完美的避开不能满足要求的那一部分呢?
想到这里,四个大字从主播的脑海里腾空而起——滑动窗口。
代码
// 暴力的话O(n ^ 4)超时// 观察矩阵越大和越大,满足单调性,考虑双指针优化
#include<iostream>
using namespace std;
const int N = 510;
typedef long long LL;
int n, m, k;
int a[N][N], s[N][N];
LL cnt;
int main()
{scanf("%d%d%d", &n, &m, &k);for(int i = 1; i <= n; i++)for(int j = 1; j <= m; j++){scanf("%d", &a[i][j]);s[i][j] = s[i - 1][j] + s[i][j - 1] + a[i][j] - s[i - 1][j - 1]; // 合并两个区间,容斥原理}for(int i = 1; i <= n; i++)for(int j = i; j <= n; j++){ //确定区间的上届和下届// 滑动窗口int l = 1, r = 0;while(++r <= m) {while(l <= r && s[j][r] - s[j][l - 1] - s[i - 1][r] + s[i - 1][l - 1] > k)l++;if(l <= r) cnt += r - l + 1;}}cout << cnt;return 0;
}
递增三元组

分析
显然暴力枚举的话会超时,需要优化。
我们将不等式拆分:
Ai < Bj
Bj < Ck
先来算下面的不等式。
要计算满足Bj < Ck,的二元组的数量可以先将B和C排序。
随后通过双指针计算出对于每一个Bj,满足的Cj的数量。
我们便得出了下面的不等式的数量,那么如何代入上面的不等式呢?
由于<的包含性,所以对于每个A,我们计算的是满足Bj < Cj二元组上的一个后缀。
所以我们先对计算出来的二元组进行前缀和操作。
随后排序A,利用双指针计算即可。
代码
// 暴力的话O(n ^ 3),会超时,考虑优化
// 随后我们发现可以排序,通过双指针逐层计算。想不出来和前缀和有什么关联
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 100010;
int n;
int a[N], b[N], c[N];
int st[N];
LL s[N];
int main()
{scanf("%d", &n);for(int i = 1; i <= n; i++)scanf("%d", a + i);for(int i = 1; i <= n; i++)scanf("%d", b + i);for(int i = 1; i <= n; i++)scanf("%d", c + i);sort(a + 1, a + n + 1); sort(b + 1, b + n + 1); sort(c + 1, c + n + 1);for(int i = 1, j = 1; i <= n && j <= n; i++){while(j <= n && c[j] <= b[i]) j++;st[i] = n - j + 1; //计算出b上每个点满足要求的数量} //前缀和for(int i = 1; i <= n; i++)s[i] = s[i - 1] + st[i];LL l = 0;for(int i = 1, j = 1; i <= n && j <= n; i++){while(j <= n && b[j] <= a[i]) j++;l += s[n] - s[j - 1];}printf("%lld", l);return 0;
}
激光炮弹

分析
要计算矩阵和,使用二维前缀和。(这道题主要是考察细节,空间上比较接近上限,要严格判断每个数据会不会溢出)
代码
#include<iostream>
using namespace std;
typedef long long LL;
const int N = 5010;
int n, r;
int s[N][N], l;
int main()
{scanf("%d%d", &n, &r);for(int i = 0; i < n; i++){int x, y, w;scanf("%d%d%d", &x, &y, &w);s[x + 1][y + 1] += w;}for(int i = 1; i < N; i++)for(int j = 1; j < N; j++)s[i][j] += s[i - 1][j] + s[i][j - 1] -s[i - 1][j - 1];if(r >= N){printf("%lld", s[N - 1][N - 1]);return 0;}for(int i = 1; i + r - 1 < N; i++)for(int j = 1; j + r - 1 < N; j++) //枚举起点l = max(l, s[i + r - 1][j + r - 1] - s[i + r - 1][j - 1] - s[i - 1][j + r - 1] + s[i - 1][j - 1]);printf("%lld", l);return 0;
}
总结
困QAQ。主播要休息了,明天是差分和双指针。
