第一章 前缀和与差分
文章目录
- 第一章 前缀和与差分
- 笔记
- 习题
- A. 智乃酱的区间乘积
- B. 智乃酱的子集与超集
- C. 智乃酱的前缀和与差分
- D. 智乃酱的静态数组维护问题多项式
- E. 智乃酱的双塔问题
- F. 牛牛的猜球游戏
- G. 牛牛的Link Power I
- H. 小w的糖果
- I. 积木大赛
- J. 道路铺设
数据结构相关知识点
第一章 前缀和与差分
笔记
前缀和:
sum[i] = sum[i - 1] + a[i]
,故a[l] + ... + a[r] = sum[r] - sum[l - 1]
- 适用:
- 乘法:例题A:智乃酱的区间乘积(注意:除法要用逆元)
- 其他操作:连续若干操作产生的叠加影响可通过某种反向操作”撤销“
- 整体转移:例题F:牛牛的猜球游戏
- 满秩矩阵的链乘法:例题E:智乃酱的双塔问题
- 卷积
- 适用:
差分:
d[i] = a[i] - a[i - 1]
,故a[i] = a[i - 1] + d[i]
- 差分数组:操作 dl+x,dr+1−xd_l+x,d_{r+1}-xdl+x,dr+1−x,前缀和后原数组在 l∼rl\sim rl∼r 均 +x+x+x
变形:加多项式
前提:最高次项为 nnn 的 nnn 阶多项式做 n+1n+1n+1 阶差分后余项为常数。
- 差分数组:操作 dl+x,dr+1−xd_l+x,d_{r+1}-xdl+x,dr+1−x,前缀和后原数组在 l∼rl\sim rl∼r 均 +x+x+x
高阶前缀和:组合数贡献
二维前缀和:
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + a[i][j]
高维(k维)前缀和 SOSDP (Sum over Subsets):子集前缀和,例题B:智乃酱的子集与超集
容斥:O(2kn)O(2^kn)O(2kn)
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + a[i][j]
SOSDP:O((k+1)n)O((k+1)n)O((k+1)n)
rep(i, 1, n): sum[i][j] = a[i][j]; rep(i, 1, n): sum[i][j] += sum[i - 1][j]; rep(i, 1, n): sum[i][j] += sum[i][j - 1];
总结:
习题
地址:第一章习题
A. 智乃酱的区间乘积
题目链接:智乃酱的区间乘积
思路:求区间连续乘积,注意 −premul[l−1]-premul[l - 1]−premul[l−1] 时要用逆元
#include<bits/stdc++.h> using namespace std; #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define per(i, a, b) for(int i = (a); i >= (b); i--) #define ll long long #define db double #define VI vector<int> #define PII pair<int, int> const db Pi = 3.141592653589793; const int INF = 0x7fffffff; const int N = 1e5 + 5; const db eps = 1e-10; const int mod = 1e9 + 7; int cas, n, m, l, r; ll a[N], premul[N]; ll inv(ll i){if(i == 1) return 1;return (mod - mod / i) * inv(mod % i) % mod; } int main(){cin >> n >> m;premul[0] = 1;rep(i, 1, n){cin >> a[i];premul[i] = (premul[i - 1] * a[i]) % mod;}while(m--){cin >> l >> r;cout << (premul[r] * inv(premul[l - 1])) % mod << endl;} } /* 5 3 5 2 3 10 6 1 5 2 3 2 5 */
B. 智乃酱的子集与超集
题目链接:智乃酱的子集与超集
类型:高阶前缀和
思路:一共 202020 个物品,显然需要状态压缩来表示。我们考虑每个组成集合的异或和,再处理出每个集合的子集,超集的异或和即可。
- 对于一共 2202^{20}220 个集合,逐一计算出其集合价值,即异或和
- 利用高阶前缀和求解(SOSDP做法):对于每个集合的子集,从低位到高位依次考虑每个集合的由来。通过对当前位已有物品的忽略,由缺少状态来计算当前状态。
- 超集同理
- 询问时,由物品添加为所求集合即可
#include<bits/stdc++.h> using namespace std; #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define per(i, a, b) for(int i = (a); i >= (b); i--) #define ll long long #define db double #define VI vector<int> #define PII pair<int, int> const db Pi = 3.141592653589793; const int INF = 0x7fffffff; const int N = 21; const db eps = 1e-10; int n, m, k, maxbit, p; ll a[N], Xor[1 << N], pre_sum[1 << N], suf_sum[1 << N]; void init(){ //预处理maxbit = (1 << n) - 1;//状压计算各集合异或和结果rep(bit, 0, maxbit){rep(i, 0, n - 1){if(bit & (1 << i)) Xor[bit] ^= a[i];}pre_sum[bit] = suf_sum[bit] = Xor[bit];}rep(i, 0, n - 1){rep(bit, 0, maxbit){//二进制位为1,表示要选if(bit & (1 << i)) pre_sum[bit] += pre_sum[bit ^ (1 << i)];//二进制位为0,表示不选else suf_sum[bit] += suf_sum[bit ^ (1 << i)];}} } int main(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);cin >> n >> m;rep(i, 0, n - 1) cin >> a[i];init();while(m--){cin >> k;int nowbit = 0;rep(i, 1, k){cin >> p;nowbit += (1 << (p - 1));}cout << pre_sum[nowbit] << " " << suf_sum[nowbit] << endl;} } /* 3 5 1 5 9 1 1 1 2 2 1 2 3 1 2 3 0 */
C. 智乃酱的前缀和与差分
题目链接:智乃酱的前缀和与差分
思路:
D. 智乃酱的静态数组维护问题多项式
题目链接:智乃酱的静态数组维护问题多项式
思路:
- 结论:kkk 次多项式做 k+1k+1k+1 次差分后为余项为常数
- 由于根据差分和前缀和均可以完全还原出原序列,则为了将所有多项式降为常数,我们可对所有序列进行 k+1k+1k+1 次差分。(本题多项式最高 555 次,故 k+1=5+1=6k+1=5+1=6k+1=5+1=6)
- 注意初始原序列进行 666 次差分
- 计算多项式函数,和 rrr 后对应相减函数,进行 666 次差分
- 统一加齐后,进行 666 次前缀和便可还原结果序列
- 求区间和可再次前缀和处理。
#include<bits/stdc++.h> using namespace std; #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define per(i, a, b) for(int i = (a); i >= (b); i--) #define ll long long #define db double #define VI vector<int> #define PII pair<int, int> const db Pi = 3.141592653589793; const int INF = 0x7fffffff; const int N = 1e5 + 5; const db eps = 1e-10; const int mod = 1e9 + 7; int cas, n, m, q, l, r, k; ll c[10], f[10], a[N]; void sum(ll a[], int n){rep(i, 1, n) a[i] = (a[i - 1] + a[i] + mod) % mod; } void diff(ll a[], int n){ //注意逆序!!!per(i, n, 1) a[i] = (a[i] - a[i - 1] + mod) % mod; } ll Pow(int x, int cnt){ll tmp = 1;rep(i, 1, cnt) (tmp *= (ll)x) %= mod;return tmp; } ll funct(int x){ll res = 0;rep(i, 0, 5)if(c[i]) (res += c[i] * Pow(x, i)) %= mod;return res; } int main(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);cin >> n >> m >> q;rep(i, 1, n) cin >> a[i];//差分预处理rep(time, 1, 6) diff(a, n);rep(i, 1, m){cin >> l >> r >> k;memset(c, 0, sizeof(c));per(j, k, 0) cin >> c[j];//加frep(i, 1, 10) f[i] = funct(i);rep(time, 1, 6) diff(f, 10);rep(i, 1, 10) (a[l - 1 + i] += f[i]) %= mod;//减frep(i, 1, 10) f[i] = funct(i + r - l + 1);rep(time, 1, 6) diff(f, 10);rep(i, 1, 10) ((a[r + i] -= f[i]) += mod) %= mod;}//还原rep(time, 1, 6) sum(a, n);//准备输出答案sum(a, n);while(q--){cin >> l >> r;cout << (a[r] - a[l - 1] + mod) % mod << endl;} } /* 10 2 11 1000 1000 1000 100000 1000 1000 10000 10000 10000 100000 1 10 0 100 1 10 1 1 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 10 1 10 */
E. 智乃酱的双塔问题
题目链接:智乃酱的双塔问题
类型:矩阵前缀和
思路:从地面到达第 iii 层楼可记录 2×22\times 22×2 矩阵 presum[i]presum[i]presum[i],这个矩阵
0 1 0 表示从左楼到左楼 表示从左楼到右楼 1 表示从右楼到左楼 表示从右楼到右楼 左上方向楼梯表示 [1101]\begin{bmatrix}1&1\\0&1 \end{bmatrix}[1011],右上方向楼梯表示 [1011]\begin{bmatrix}1&0\\1&1 \end{bmatrix}[1101],求出前缀矩阵。
对于需要楼层区间,只需要利用逆矩阵代替相减即可
注意矩阵乘法和逆矩阵的求解,见代码。
//矩阵前缀和 #include<bits/stdc++.h> using namespace std; #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define per(i, a, b) for(int i = (a); i >= (b); i--) #define ll long long #define db double #define VI vector<int> #define PII pair<int, int> const db Pi = 3.141592653589793; const int INF = 0x7fffffff; const int N = 1e5 + 5; const db eps = 1e-10; const int mod = 1e9 + 7; int n, m, src, des, ps, pt; string s; ll A[5][5]; struct Mat{ll a[2][2];Mat(int a1 = 0, int a2 = 0, int b1 = 0, int b2 = 0){a[0][0] = a1, a[0][1] = a2;a[1][0] = b1, a[1][1] = b2;} }presum[N]; Mat left_to_up = Mat(1, 1, 0, 1); Mat right_to_up = Mat(1, 0, 1, 1);Mat operator * (Mat x, Mat y){Mat ans = Mat(0, 0, 0, 0);rep(i, 0, 1) rep(j, 0, 1) rep(k, 0, 1){(ans.a[i][j] += x.a[i][k] * y.a[k][j] % mod) %= mod;}return ans; } ll inv(ll x){if(x == 1) return 1;return (mod - mod / x) * inv(mod % x) % mod; } Mat getinv(Mat x, int n){ //求逆矩阵//构造rep(i, 0, n - 1) rep(j, 0, n - 1){A[i][j] = x.a[i][j];A[i][j + n] = (j == i ? 1 : 0);}//化为对角矩阵rep(j, 0, n - 1){//选择主元if(!A[j][j]){rep(i, j + 1, n - 1){if(!A[i][j]) continue;//行交换rep(k, 0, 2 * n - 1) swap(A[i][k], A[j][k]);break;}}//主元化为1int inva = inv(A[j][j]);rep(k, 0, 2 * n - 1) (A[j][k] *= inva) %= mod;//消元rep(i, j + 1, n - 1){int mul = A[i][j];rep(k, 0, 2 * n - 1) A[i][k] = (A[i][k] - mul * A[j][k] % mod + mod) % mod;}}//左半部分化为单位阵per(j, n - 1, 0)per(i, j - 1, 0){int mul = A[i][j];rep(k, 0, 2 * n - 1) A[i][k] = (A[i][k] - mul * A[j][k] % mod + mod) % mod;}Mat res;rep(i, 0, n - 1) rep(k, 0, n - 1) res.a[i][k] = A[i][k + n];return res; } int main(){cin >> n >> m >> s;presum[1] = Mat(1, 0, 0, 1);rep(i, 2, n){if(s[i - 2] == '/') presum[i] = presum[i - 1] * left_to_up;else presum[i] = presum[i - 1] * right_to_up;}while(m--){cin >> src >> des >> ps >> pt;// -sum[l - 1] + sum[r]Mat ans = getinv(presum[src], 2) * presum[des];cout << ans.a[ps][pt] << endl;} } /* 4 5 //\ 1 4 0 0 2 4 0 0 2 4 0 1 3 4 0 1 3 4 1 0 */
F. 牛牛的猜球游戏
题目链接:牛牛的猜球游戏
类型:序列排列变换前缀和
思路:每次排列都是一种全部改变,且有连续性。故可作为前缀和统计,在查找时需要定义减法操作,逆向还原即可。
//数组排列前缀和 #include<bits/stdc++.h> using namespace std; #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define per(i, a, b) for(int i = (a); i >= (b); i--) #define ll long long #define db double #define VI vector<int> #define PII pair<int, int> const db Pi = 3.141592653589793; const int INF = 0x7fffffff; const int N = 1e5 + 5; const db eps = 1e-10; int cas, n, m, a, b, l, r; struct AC{int presum[10]; }matrix[N]; AC operator - (AC a, AC b){ //a - bAC ans, tmp;rep(i, 0, 9) tmp.presum[b.presum[i]] = i;rep(i, 0, 9) ans.presum[i] = tmp.presum[a.presum[i]];return ans; } void Print(AC x){rep(i, 0, 9) printf("%d ", x.presum[i]);printf("\n"); } int main(){cin >> n >> m;rep(i, 0, 9) matrix[0].presum[i] = i;rep(i, 1, n){scanf("%d%d", &a, &b);matrix[i] = matrix[i - 1];swap(matrix[i].presum[a], matrix[i].presum[b]);}while(m--){scanf("%d%d", &l, &r);Print(matrix[r] - matrix[l - 1]);} } /* 5 3 0 1 1 2 2 3 0 1 9 0 1 5 5 5 3 5 */
G. 牛牛的Link Power I
题目链接:牛牛的Link Power I
思路:对于 iii 处的节点对后面位置的影响为
a:ia:0123456d:0111111dd:0100000\begin{aligned} a:\;&i\\ a:\;&0123456\\ d:\;&0111111\\ dd:\;&0100000 \end{aligned} a:a:d:dd:i012345601111110100000
即两次差分后化为常数序列,则对所有数字 111 的位置处理,最后两次前缀和还原原序列后计算对应 111 的位置即可。#include<bits/stdc++.h> using namespace std; #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define per(i, a, b) for(int i = (a); i >= (b); i--) #define ll long long #define db double #define VI vector<int> #define PII pair<int, int> const db Pi = 3.141592653589793; const int INF = 0x7fffffff; const int N = 1e5 + 5; const db eps = 1e-10; const int mod = 1e9 + 7; ll n, a[N], effect[N], tmp[N], ans = 0; char ch; vector<int> store; int main(){cin >> n;store.clear();rep(i, 1, n){cin >> ch;a[i + 1] = (int)(ch - '0');if(ch == '1') store.push_back(i);}rep(i, 1, n) tmp[i] = (tmp[i - 1] + a[i]) % mod;rep(i, 1, n) effect[i] = (effect[i - 1] + tmp[i]) % mod;for(auto i : store) (ans += effect[i]) %= mod;cout << ans << endl; } /* 5 00110 */
H. 小w的糖果
题目链接:小w的糖果
思路:对于加等差序列和平方序列,计算发现
等差序列中差分两次为常数
a:1,2,3,4,5,6d:1,1,1,1,1,1dd:1,0,0,0,0,0\begin{aligned} a:\;&1,2,3,4,5,6\\ d:\;&1,1,1,1,1,1\\ dd:\;&1,0,0,0,0,0 \end{aligned} a:d:dd:1,2,3,4,5,61,1,1,1,1,11,0,0,0,0,0平方序列中差分两次为常数
a:1,4,9,16,25,36d:1,3,5,7,9,11dd:1,2,2,2,2,2ddd:1,1,0,0,0,0\begin{aligned} a:\;&1,4,9,16,25,36\\ d:\;&1,3,5,\;\;7,\;\;9,11\\ dd:\;&1,2,2,\;\;2,\;\;2,\;\;2\\ ddd:\;&1,1,0,\;\;0,\;\;0,\;\;0\\ \end{aligned} a:d:dd:ddd:1,4,9,16,25,361,3,5,7,9,111,2,2,2,2,21,1,0,0,0,0于是可对三种操作分别预处理,最后不同次数前缀和还原即可。
#include<bits/stdc++.h> using namespace std; #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define per(i, a, b) for(int i = (a); i >= (b); i--) #define ll long long #define db double #define VI vector<int> #define PII pair<int, int> const db Pi = 3.141592653589793; const int INF = 0x7fffffff; const int N = 1e5 + 5; const db eps = 1e-10; const int mod = 1e9 + 7; int cas, n, m; ll d1[N], d2[N], d3[N]; void init(){rep(i, 1, n) d1[i] = d2[i] = d3[i] = 0; } void pre_sum(ll a[]){rep(i, 1, n) a[i] = (a[i - 1] + a[i]) % mod; } int main(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);cin >> cas;while(cas--){cin >> n >> m;init();rep(i, 1, m){int type, pos; cin >> type >> pos;if(type == 1) d1[pos]++;if(type == 2) d2[pos]++;if(type == 3) d3[pos]++, d3[pos + 1]++;}pre_sum(d1);pre_sum(d2), pre_sum(d2);pre_sum(d3), pre_sum(d3), pre_sum(d3);rep(i, 1, n) cout << (d1[i] + d2[i] + d3[i]) % mod << " ";cout << endl;} } /* 4 10 1 1 1 10 1 2 2 10 1 3 3 10 3 1 1 2 2 3 3 */
I. 积木大赛
题目链接:积木大赛(来源:NOIP2013)
思路:差分处理计算正数即可
#include<bits/stdc++.h> using namespace std; #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define per(i, a, b) for(int i = (a); i >= (b); i--) #define ll long long #define db double #define VI vector<int> #define PII pair<int, int> const db Pi = 3.141592653589793; const int INF = 0x7fffffff; const int N = 1e5 + 5; const db eps = 1e-10; int n; ll a[N], d[N], ans; //差分看正数 int main(){cin >> n;rep(i, 1, n){cin >> a[i];d[i] = a[i] - a[i - 1];if(d[i] > 0) ans += d[i];}cout << ans << endl; } /* 5 2 3 4 1 2 */
J. 道路铺设
题目链接:道路铺设(来源:NOIP2018)
思路:同上题
#include<bits/stdc++.h> using namespace std; #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define per(i, a, b) for(int i = (a); i >= (b); i--) #define ll long long #define db double #define VI vector<int> #define PII pair<int, int> const db Pi = 3.141592653589793; const int INF = 0x7fffffff; const int N = 1e5 + 5; const db eps = 1e-10; ll n, a[N], d[N], ans; int main(){cin >> n;rep(i, 1, n){cin >> a[i];d[i] = a[i] - a[i - 1];if(d[i] > 0) ans += d[i];}cout << ans << endl; } /* 6 4 3 2 5 3 5 */
第一章 前缀和与差分相关推荐
- c++算法基础必刷题目——前缀和与差分
文章目录 前缀和与差分算法: 1.校门外的树 2.值周 3.中位数图 4.激光炸弹 5.二分 6.货仓选址 前缀和与差分算法: 前缀和与差分算法主要是为了快速求出某个区间的和,例如有一个数组a[1 ...
- 前缀和与差分那些不得不说的事(一维,二维)公式与图解详细解说
前言: 最近实验室的小伙伴们都开始学习前缀和和差分了呢,阿皓也发现这两个东西确实是不容易理解啊.尤其是那个二维差分,很容易跟别的东西弄混,甚至有些同学们现在还不知道二维差分是个什么东东.所以呢,阿皓就 ...
- vtk教程第一章介绍
可视化-"2:用视觉术语解释或将其转化为视觉形式的行为或过程",<韦伯斯特第九部新大学词典>收录. 1.1什么是可视化 想象是我们日常生活的一部分.从天气图到娱乐行业令 ...
- 王道考研 计算机网络笔记 第一章:概述计算机网络体系结构
本文基于2019 王道考研 计算机网络: 2019 王道考研 计算机网络 个人笔记总结 后续章节将陆续更新- 目录 一.概念.功能.组成.分类 1. 计算机网络的概念 2. 计算机网络功能 3. 计算 ...
- 红书《题目与解读》第一章 数学 题解《ACM国际大学生程序设计竞赛题目与解读》
整理的算法模板合集: ACM模板 点我看算法全家桶系列!!! 实际上是一个全新的精炼模板整合计划 红书<题目与解读>第一章 数学 题解<ACM国际大学生程序设计竞赛题目与解读> ...
- 0x03.基本算法 — 前缀和与差分
目录 一.前缀和 二.二维前缀和 1.二维前缀和的修改和求和 0. NOI 2003激光炸弹(二维前缀和) 1.牛妹吃豆子(二维前缀和模板,修改+求和) 2.静态数组的区间求和问题 3.静态维护区间加 ...
- windows核心编程-第一章 对程序错误的处理
第一章-对程序错误的处理 在开始介绍Microsoft Windows 的特性之前,必须首先了解 Wi n d o w s的各个函数是如何进行错误处理的. 当调用一个Wi n d o w s函数时,它 ...
- Python第一章-基础知识
第一章:基础知识 1.1 安装python. 直接官网下载最新的python然后默认安装就可以了,然后开始菜单里找到pyhton *.*.* Shell.exe运行python的交互shell ...
- Python计算机视觉:第一章 图像处理基础
第一章 图像处理基础 1.1 PIL-Python图像库 1.1.1 对图片进行格式转换 1.1.2 创建缩略图 1.1.3 拷贝并粘贴区域 1.1.4 调整尺寸及旋转 1.2 Matplotlib库 ...
- 《Go语言圣经》学习笔记 第一章 Go语言入门
Go语言圣经学习笔记 第一章 Go语言入门 目录 Hello, World 命令行参数 查找重复的行 GIF动画 获取URL 并发获取多个URL Web服务 本章要点 注:学习<Go语言圣经&g ...
最新文章
- R语言使用caret包的confusionMatrix函数计算混淆矩阵、使用编写的自定义函数可视化混淆矩阵(confusion matrix)
- 打开 谷歌浏览器exe_谷歌浏览器下载安装和插件安装步骤
- 使用axios时遇到的Request Method: OPTIONS请求,会同时发送两次请求问题
- Android学习笔记四十Preference使用
- php 多维数组 列,总结PHP实现提取多维数组指定一列的方法
- iOS 转盘动画效果实现
- pc-H5 适配方案
- filter以及reduce的用法
- Ubuntu 安装 gcc 过程
- # c++万能头文件
- php提升并发,php高并发处理
- mysql的identity_Mysql中Identity 详细介绍
- Webtoos 仿Q+云桌面框架
- Leetcode C语言 “加一”
- 2021年web前端基础面试题
- 拓展交流空间,分享开发精彩 | 开发者说·DTalk 鉴赏
- 网易我的世界服务器怎么显示键盘,网易我的世界指令怎么用(常见的指令及使用方式)...
- IIS6.0 asp.asa.cer.cdx. 原理
- 监控易这个基本功,让信创产品落地即具备“可观测性”
- MBA-day7 逻辑学-选言和假言的推理题.md
热门文章
- 微信高级群发接口 {errcode:40008,errmsg:invalid message type hint: [aRIDBA0726age9]}
- qt样式表设置边框_Qt样式表之 QSS 语法介绍
- ImportError: DLL load failed while importing QtWidgets: 找不到指定的程序
- 未对销售组织 XXX 分销渠道 XX 语言 ZH 定义
- cookie—基于js的coolie使用
- 前端构建和模块化工具-coolie
- cs5460a c语言程序,cs5460a应用电路(含源程序)
- python写的ROS激光雷达扇形滤波
- 旅游网站设计参考文献优秀范例合集
- SPF算法简单解析过程