题单
P3131 [USACO16JAN]Subsequences Summing to Sevens S
思路
题干很简单,就是要求最长的区间[l,r],满足7 | (a[l] + a[l + 1] + …… + a[r]),但是如果对每一个区间硬求和的话时间复杂度会加大,于是不难想到用线性前缀和。但是就算这样,发现时间复杂度还是高达O(n^2),于是应该还可以在优化。对于区间[l,r]如果7 | (pre[r] - pre[l - 1]),则不难得到pre[r]与pre[l - 1]在模7的意义下是相等的,而这个值只能是k,其中k属于{0, 1, 2, 3, 4, 5, 6}。那我们便可对于每个k,在pre数组中找到下标最小(记为q)的和下标最大(记为h)的值为k的元素,如果能找到,那么ans = max (ans, h - j);否则直接跳过;但是需要警戒k=0的时候是特殊的,因为这个时候pre[i]与pre[j]其实已经被7整除了,那么ans = max (ans, j)。时间复杂度O(n)
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 50005, mod = 7;
int n, a[maxn], prea[maxn], ans = -1;
int q (int x)
{
for (int i = 1; i <= n; i++)
{
if (prea[i] == x)
{
return i;
}
}
return -1;
}
int h (int x)
{
for (int i = n; i >= 1; i--)
{
if (prea[i] == x)
{
return i;
}
}
return -1;
}
int main ()
{
scanf ("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf ("%d", &a[i]);
a[i] %= mod;
prea[i] = (prea[i - 1] + a[i]) % mod;
}
for (int i = 0; i <= 6; i++)
{
int qi = q (i), hi = h (i);
if (qi == -1)
{
continue;
}
if (i == 0)
{
ans = max (qi, hi);
}
else
{
ans = max (ans, hi - qi);
}
}
printf ("%d", ans);
return 0;
}
收获
①水一个勤思考,以优化代码
P1387 最大正方形
思路
由题意即寻找边长最大的正方形,使得其中的元素全为1;进而可以转化为:寻找边长(记为l)最大的正方形,使得其中元素的和为l^2,求元素和,我们要是一个一个累加,则十分麻烦。于是想到用二维前缀和。然后我们有发现对于答案(即边长),它具有有界性与单调性:有界性,它肯定在[0, min (n, m)];单调性:对于边长大于答案的正方形,其一定不能满足题意。又对边长进行二分答案,可以极大的简化时间复杂度,故我们作此操作时间复杂度:O(log (min (n, m)) * n * m)
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 105;
int n, m, a[maxn][maxn], pre[maxn][maxn], l, r, ans;
bool check (int x)
{
for (int i = 1; i + x - 1 <= n; i++)
{
for (int j = 1; j + x - 1<= m; j++)
{
int di = i + x - 1, dj = x + j - 1;
if (pre[di][dj] - pre[i - 1][dj] - pre[di][j - 1] + pre[i - 1][j - 1] == x * x)
{
return true;
}
}
}
return false;
}
int main ()
{
scanf ("%d %d", &n, &m);
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
scanf ("%d", &a[i][j]);
pre[i][j] = pre[i - 1][j] + pre[i][j - 1] - pre[i - 1][j - 1] + a[i][j];
}
}
l = 0, r = min (n, m);
while (l <= r)
{
int mid = (l + r) / 2;
if (check (mid))
{
ans = mid;
l = mid + 1;
}
else
{
r = mid - 1;
}
}
printf ("%d", ans);
return 0;
}
收获
①学会用画图形象理解的方式,构建二位前缀和数组以及求矩形区域中的值。
P3397 地毯
思路
不难看出,这个题就是需要我们在适当的时间复杂度内,随着有关毯子信息的输入,动态地处理每一个坐标点被几个毯子覆盖,于是,我们想到用二维差分
。时间复杂度为O(n^2)
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1005;
int n, m, a[maxn][maxn], diff[maxn][maxn];
int xi, xii, yi, yii;
int main ()
{
scanf ("%d %d", &n, &m);
for (int i = 1; i <= m; i++)
{
scanf ("%d %d %d %d", &xi, &yi, &xii, &yii);
diff[xi][yi] ++;
diff[xi][yii + 1] --;
diff[xii + 1][yi] --;
diff[xii + 1][yii + 1] ++;
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
a[i][j] = a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1] + diff[i][j];
printf ("%d ", a[i][j]);
}
printf ("\n");
}
return 0;
}
收获
P2280 [HNOI2003]激光炸弹
思路
发现与P1387 最大正方形有点相似,于是我们会选择用二维前缀和,但是我们这里不用二分答案了,因为正方形的边长已知,故我们只需要遍历一遍即可。但要注意m与我们的“价值图”的长和宽之间的大小以及数组越界问题(因为坐标点下标由0开始,而不是1)
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5005;
int n, m, pre[maxn][maxn], ans, xi, yi;
int x, y, v;
int main ()
{
scanf ("%d %d", &n, &m);
for (int i = 1; i <= n; i++)
{
scanf ("%d %d %d", &x, &y, &v);
pre[x + 1][y + 1] += v; // 防止数组下标越界
xi = max (xi, x + 1);
yi = max (yi, y + 1);
}
xi = max (xi, m); // 使得m大于整个“价值图”的长与宽时也能正常的枚举遍历。
yi = max (yi, m); //同上
for (int i = 1; i <= xi; i++)
{
for (int j = 1; j <= yi; j++)
{
pre[i][j] += pre[i - 1][j] + pre[i][j - 1] - pre[i - 1][j - 1];
}
}
for (int i = 1; i + m - 1 <= xi; i++)
{
for (int j = 1; j + m - 1 <= yi; j ++)
{
int dx = i + m - 1, dy = j + m - 1;
v = pre[dx][dy] - pre[dx][j - 1] - pre[i - 1][dy] + pre[i - 1][j - 1];
ans = max (ans, v);
}
}
printf ("%d", ans);
return 0;
}
收获
①只能说,下次类似的题,要注意m与我们的“价值图”的长和宽之间的大小关系问题
P4552 [Poetize6] IncDec Sequence
偷懒
收获
这个题思路需要领会
最后的总结
①这一小节我们学习了线性前缀和、线性差分、二维前缀和、二维差分,在其中屑觉得要重点理解二维差分。
②前缀和用于预处理静态的某一数组,以便在我们想要获取某一区域内的值时,可以线性的获取;差分用于实现线性处理动态变化的数组;前缀和与差分互为逆过程。
③有些题要求我们不能仅刻板的使用前缀和与差分,而是要在其知识的基础上让其根据妙用。