前缀和&差分专题
P3078 Poker Hands S
分析:
这题跟P4552很相似 只是少了个需求(求方案个数)
我们可以这样分析:
对该数据求取差分数组后,所以的牌被打出,即是差分数组全部为0,那么我们的目标也就转化为了如何用最少的次数去使得该查分数数组全部为0,因为 在原数组上删去一个区间的数 等价于 在差分数组上 两个端点- + tar
所以 这里就涉及到一个简单的贪心思想
: 如果一个操作能同时使得 负数和正数同时跟接近0
那么一定要先把这种操作执行完 再加上剩下的操作即为最小次数
证明
贪心思路即为证明:
如果一个操作能同时使得 负数和正数同时跟接近0 那么一定要先把这种操作执行完 再加上剩下的操作即为最小次数
思路
求出差分数组中正数之和的绝对值和负数之和的绝对值的最大值即可
代码
O(n)
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 100010;
int a[N];
int main() {
int n;
cin >>n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = n; i > 1; i--) a[i] -= a[i - 1];
LL pos = 0, neg = 0;
for (int i = 2; i <= n; i++) {
if (a[i] > 0) pos += a[i];
else neg -= a[i];
}
cout << max(pos, neg) << endl;
cout << (abs(pos - neg) + 1) << endl;
return 0;
}
P3397 地毯
分析
二维差分模板题
代码
#include <iostream>
using namespace std;
const int N = 1010;
int g[N][N];
int m, n;
void insert(int x1, int y1, int x2, int y2) {
g[x1][y1] += 1;
g[x1][y2 + 1] -= 1;
g[x2 + 1][y1] -= 1;
g[x2 + 1][y2 + 1] += 1;
}
int main() {
cin >> n >> m;
while (m --) {
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
insert(x1, y1, x2, y2);
}
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= n; ++ j) {
g[i][j] += g[i - 1][j] + g[i][j - 1] - g[i - 1][j - 1];
cout << g[i][j] << " ";
}
puts("");
}
return 0;
}
P1719 最大加权矩形
分析
二维前缀和模板题
代码
#include <iostream>
using namespace std;
const int N = 150;
int q[N][N];
int n;
int main() {
cin >> n;
for (int i = 1; i <= n; ++ i)
for (int j = 1; j <= n; ++ j)
cin >> q[i][j], q[i][j] += q[i - 1][j] + q[i][j - 1] - q[i - 1][j - 1];
int res = 0;
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= n; ++ j) {
for (int k = i; k <= n; ++ k) {
for (int z = j; z <= n; ++ z) {
res = max(res, q[k][z] - q[k][j - 1] - q[i - 1][z] + q[i - 1][j - 1]);
}
}
}
}
cout << res;
return 0;
}
P5638 光骓者的荣耀
分析
一维前缀和模板题
求总最短路等价于求连续K区间的最长路
然后用总路减去最长路即可
代码
#include <iostream>
using namespace std;
const int N = 1000010;
typedef unsigned long long ULL;
ULL q[N];
int n, k;
int main() {
cin >> n >> k;
if (k >= n) {
cout << 0;
return 0;
}
for (int i = 1; i <= n - 1; ++ i) cin >> q[i], q[i] += q[i - 1];
int l = 0, r = l + k - 1;
ULL ma = 0;
for (;r <= n - 1; r ++, l ++)
ma = max(ma, q[r] - q[l - 1]);
ULL res = q[n - 1] - ma;
cout << res;
return 0;
}
P1115 最大子段和
分析
设 dp[n] 表示为以第n个数结尾的最大的子段和
那么可以得出 dp[n] 可以由两种方式转移而来
dp[n] = q[n] + dp[n - 1]
dp[n] = q[n]
1 表示 该位置与前面连在一起 2 表示 该位置单独成一个子段
所以 得出dp[n] = max(dp[n - 1] + q[n], q[n])
可以简化为判断dp[n - 1] 是否大于0
代码
O(n)
#include <iostream>
using namespace std;
const int N = 100010;
const int FINF = -0x3f3f3f3f;
int q[N];
int dp[N];
int n;
int main() {
cin >> n;
for (int i = 1; i <= n; ++ i) cin >> q[i];
int res = FINF;
dp[1] = q[1];
for (int i = 2; i <= n; ++ i) {
q[i] = max(q[i], q[i] + q[i - 1]);
res = max(res, q[i]);
}
cout << res;
return 0;
}
P3131 Subsequences Summing to Sevens S
分析
如果我们直接枚举左右断电O(n ** 2) 由于数据量过大肯定过不去的
那么我们想想再枚举过程中有没有什么信息是我们可以用到的
我们发现当 当一个子段左端点%7 的值如果等于 右端点%7的值
那么
这个区间的数就是可以被7整除的 那么我们就可以用hash表存储某个数第一次出现的位置和最后一次出现的位置 然后找到最远值即可
证明
L % 7 == x && R % 7 == x
代表 从1 - L的数据之和 %7 == x 和 1 - R 的数%7 == x 用后者减去前者得到 L + 1 - R 的数%7 == 0 即L + 1 - R
区间的数能被7整除
代码
O(n)
#include <iostream>
using namespace std;
const int N = 50010;
typedef long long LL;
LL q[N];
int hi[N];
int ha[N];
int n;
int main() {
cin >> n;
int res = 0;
for (int i = 1; i <= n; ++ i) {
cin >> q[i], q[i] += q[i - 1];
ha[q[i] % 7] = i;
if (!hi[q[i] % 7]) hi[q[i] % 7] = i;
}
for (int i = 0; i < 7; ++ i)
if (ha[i]) res = max(res, ha[i] - hi[i]);
cout << max(res, ha[0]);
return 0;
}
P2004 领地选择
分析
二维前缀和模板题
代码
#include <iostream>
using namespace std;
const int N = 1010;
const int INF = 0x3f3f3f3f;
int q[N][N];
int n, m, k;
int main() {
cin >> n >> m >> k;
for (int i = 1; i <= n; ++ i)
for (int j = 1; j <= m; ++ j)
cin >> q[i][j], q[i][j] += q[i - 1][j] + q[i][j - 1] - q[i - 1][j - 1];
int res1, res2;
int ma = -INF;
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= m; ++ j) {
int x1 = i, y1 = j, x2 = i + k - 1, y2 = j + k - 1;
if (x2 > n || y2 > m) continue;
int cur = q[x2][y2] - q[x2][y1 - 1] - q[x1 - 1][y2] + q[x1 - 1][y1 - 1];
if (cur > ma)
ma = cur, res1 = i, res2 = j;
}
}
cout << res1 << " " << res2 << endl;
}
P3406 海底高铁
分析
我们先分析什么时候选择买票 什么时候选择办卡
买票的钱是 坐该车的次数n
* 票钱
办卡的钱是 n * 特价 + 办卡钱
显然 那个便宜我们就选择哪个
为了计算出哪个便宜我们需要统计经过了那些站以及每个站走了多少次
由于我们只能挨着做车 那么题目给出的数据等价于 在q[i], q[i + 1]
之间同时加1这就能想到用我们的差分来简化运算 由于存在大数到小数 和小数到大数两种情况,所以在构造差分数组的时候记得加入修正因子
(不用在意,这是我原创的词)
代码
#include <iostream>
using namespace std;
const int N =100010;
typedef long long LL;
int a[N], b[N], c[N];
int n, m;
int q[N];
int cc[N];
void insert(int x, int y, int c) {
cc[x + c] += 1, cc[y + c + 1] -= 1;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= m; ++ i) cin >> q[i];
for (int i = 1; i <= m - 1; ++ i) {
int x = q[i], y = q[i + 1];
if (x > y) {
insert(y, x - 1, 0);
} else {
insert(x + 1, y, -1);
}
}
for (int i = 1; i < n; ++ i) cin >> a[i] >> b[i] >> c[i];
LL res = 0;
for (int i = 1; i < n; ++ i) cc[i] += cc[i - 1];
for (int i = 1; i < n; ++ i)
res += min(c[i] + 1ll * cc[i] * b[i], 1ll * cc[i] * a[i]);
cout << res;
return 0;
}
P1387 最大正方形
分析
判断一个区域是不是全是0其实就是求该区域的二维前缀和
而最大区间 分析题目 :
1.如果 有3 * 3 的正方形存在 那么一定有 3 * 3 一下的正方形
2.如果最大为 3 * 3 的正方形 那么一定不存在 4 * 4 的正方形(这句话等价于上句话)
所以数据具有二段性 那么很容易想到用二分去寻找这个最大值
代码
O(n**2 log(n))
#include <iostream>
using namespace std;
const int N = 110;
int q[N][N];
int n, m;
bool check(int x) {
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= m; ++ j) {
int x1 = i, y1 = j, x2 = i + x - 1, y2 = j + x -1;
if (x2 > n || y2 > m) continue;
if (q[x2][y2] - q[x2][y1 - 1] - q[x1 - 1][y2] + q[x1 - 1][y1 - 1] == x * x) return true;
}
}
return false;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; ++ i)
for (int j = 1; j <= m; ++ j)
cin >> q[i][j], q[i][j] += q[i - 1][j] + q[i][j - 1] - q[i - 1][j - 1];
int l = 0, r = min(n, m);
while (l < r) {
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
cout << l;
return 0;
}
P2280 激光炸弹
分析
二维前缀和模板题
代码
#include <iostream>
using namespace std;
const int N = 5010;
int a[N][N];
int main() {
int nn, r;
cin >> nn >> r;
r = min(r, 5001);
int m = 0, n = 0;
while (nn--) {
int x1, y1, v;
cin >> x1 >> y1 >> v;
a[x1 + 1][y1 + 1] += v;
}
int res = 0;
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];
}
}
for (int i = r; i < N; i++) {
for (int j = r; j < N; j++) {
res = max(res, a[i][j] - a[i - r][j] - a[i][j - r] + a[i - r][j - r]);
}
}
cout << res << endl;
return 0;
}
P3353 在你窗外闪耀的星星\
分析
一维前缀和模板题
代码
#include <iostream>
using namespace std;
const int N = 100010;
typedef long long LL;
LL q[N];
int n, w;
int main() {
cin >> n >> w;
LL res = 0;
for (int i = 1; i <= n; ++ i) {
int x, b;
cin >> x >> b;
q[x] += b;
}
for (int i = 1; i <= n; ++ i) q[i] += q[i - 1];
for (int l = 1; l <= n; ++ l) {
int r = l + w - 1;
if (r > n) break;
res = max(res, q[r] - q[l - 1]);
}
cout << res;
return 0;
}
P4552 IncDec Sequence
本题与 算法竞赛进阶指南中 增减序列一致
且 与第一题类似
分析
1.若一个数组中的数一样即其对应的差分数组除第一位外其余全为0
对于最小操作的数的求法 与第一题类似
如果一个操作能同时使得 负数和正数同时跟接近0 那么一定要先把这种操作执行完 再加上剩下的操作即为最小次数
但是由于我们不需要全为0 只需要后面的数全与第一位相同即可
所以我们是不需要统计差分数组的第一位的
max(正数, 负数)
2.对于如何求数组最终形态的个数
由于差分数组除第一位外其他全为0 那么该问题等价于 后面全为0 后第一位能有多少种数
当我们将能同时处理掉的数除去后(因为他们不会影响到第一个数 所以我们可以直接忽略) 假如正数更多 那么我们可以得到这样一个差分数组
x a1 a2 a3 a4 a5 a5
(ai >=0) 为了让ai都为0 我们有两种操作方法
- 将第一位 + 1 再将 该位 -1
- 将改位 -1 再将 结尾尾 +1(注: 此处指的 最后一位是无意义的 只是为了防止越界用的)
第一个操作等价于将该位置前的所有数+ai
第二个操作等价于将该位置后的所有数-ai
所以 不难算出 差分数组的头一位 有 sum(ai ) + 1总可能
即 abs(正数 - 负数) + 1;
举例子理解 :
(1表示进行操作一
2表示进行操作二)
1 0 0 2 0 0
方案一 2, 2
方案二 1, 2
方案三 1, 1
代码
O(n)
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 100010;
int a[N];
int main() {
int n;
cin >>n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = n; i > 1; i--) a[i] -= a[i - 1];
LL pos = 0, neg = 0;
for (int i = 2; i <= n; i++) {
if (a[i] > 0) pos += a[i];
else neg -= a[i];
}
cout << max(pos, neg) << endl;
cout << (abs(pos - neg) + 1) << endl;
return 0;
}
P2671 求和
分析
由于 y−x=z−y且都为整数 那么 x + y 一定是偶数
所以 x 与 y 的奇偶性一定一致
又又题意得 x 与 y 对应的颜色相同
所以 我们可以按照 奇偶性 和 颜色 给所有的编号分类
每一个类别里的任意两个数是可以组成三元组的(中间数无影响)
但是如果枚举的话O(n ** 2)的复杂度是过不了的
于是我们可以进行这样的优化:
最后我们只需要一次遍历即可算出所有三元组的和
代码
O(n)
#include <iostream>
#include <vector>
using namespace std;
const int N = 100010;
const int MOD = 10007;
typedef long long LL;
typedef pair<int, int> PII;
#define x first
#define y second
int q[N], c[N];
vector<PII> col[N][2];
int n, m;
LL res;
int main() {
cin >> n >> m;
for (int i = 0; i < n; ++i) cin >> q[i];
for (int i = 0; i < n; ++i) cin >> c[i];
for (int i = 0; i < n; ++i)
if (!(i % 2)) col[c[i]][0].push_back({ i + 1, q[i] });
else col[c[i]][1].push_back({ i + 1, q[i] });
for (int i = 1; i <= m; ++i) {
LL sum = 0;
int n = col[i][0].size();
for (int j = 0; j < n && n >= 2; ++j) sum += col[i][0][j].y;
for (int j = 0; j < n && n >= 2; ++j) {
res += (col[i][0][j].x * ((n - 2) * col[i][0][j].y + sum)) % MOD;
res %= MOD;
}
sum = 0;
n = col[i][1].size();
for (int j = 0; j < n && n >= 2; ++j) sum += col[i][1][j].y;
for (int j = 0; j < n && n >= 2; ++j) {
res += (col[i][1][j].x * ((n - 2) * col[i][1][j].y + sum)) % MOD;
res %= MOD;
}
}
cout << res;
return 0;
}
本前缀和和差分专题就到此结束啦 完结撒花!!!
大家有什么疑问欢迎提出!