算法竞赛进阶指南0.3 前缀和与差分
注:此博客为学习acwing视频的随手的笔记
目录
1.激光炸弹 (二维前缀和)
2. IncDec序列 (差分)
3. Tallest Cow (差分)
前缀和与差分
前缀和与差分是一个互逆的运算。
二维前缀和:
1)
for(int i = 1 ; i <= n ; i ++) //二维前缀和
for(int j = 1 ; j <= m ; j ++)
sum[i][j] += sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1];
2)
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
sum[i][j] +=sum[i][j-1];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
sum[i][j] += sum[i-1][j];
1.激光炸弹 (二维前缀和)
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 5005;
typedef long long ll;
int N , R ;
int sum[maxn][maxn];
int main(){
cin.sync_with_stdio(false);
cin.tie(0);
cin >> N >> R;
int n = R , m = R;
for(int i = 0 ; i < N ; i ++){
int x, y, z;
cin >> x >> y >> z;
sum[++x][++y] = z; //从 1 开始 不需要再处理边界问题
n = max(n , x) , m = max(m , y); //这道题的长宽需要自己判
}
for(int i = 1 ; i <= n ; i ++) //二维前缀和
for(int j = 1 ; j <= m ; j ++)
sum[i][j] += sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1];
int ans = 0;
for(int i = R ; i <= n ; i ++)
for(int j = R ; j <= m ; j++)
ans = max(ans , sum[i][j] - sum[i - R][j] - sum[i][j - R] + sum[i - R][j - R]);
cout << ans << endl;
return 0;
}
2. IncDec序列 (差分)
差分: 前缀和的逆运算
a[1] , a[2] , a[3] , a[4] , ... , a[n]
b[i] = a[i] - a[i - 1] , b[1] = a[1]
b是a的差分序列 a是b的前缀和序列
O(1) 给区间[l , r]加上一个常数C :
b[l] += C , b[r + 1] -= C
它的效果就是前缀和序列a从l ~r加C 其它不影响
- 先求出序列的差分序列b
- 做以下操作 , 使得 b[2] ~b[n]变为0
1) 选择两个数 2 <= i ,j <= n b[i] += 1 b[j] -= 1 或 b[i] -= 1 b[j] += 1
2) i = 1 , 2 <= j <= n
3) 2 <= i <= n , j = n + 1
4) i = 1 , j = n + 1 (无意义)
贪心1) 操作 前面加1 后面-1这样的 (可以保证最小次数)
操作1最多能够选择 min(差分序列正数和 , 差分序列负数绝对值和)
若正数多了 , 则进行操作二 或 操作三 均可
例如:操作1结束后 , 正数还剩5 ,那么可以选择
0次操作二 , 5次操作三 1次操作二 , 4次操作三
2次操作二 , 3次操作三 3次操作二 , 2次操作三
4次操作二 , 1次操作三 5次操作二 , 0次操作三
六种方式 六种结果
综上 : 最小操作次数 = 操作1 + 操作2,3
= min(正数的和 , 负数的和的绝对值) + abs(正数的和-负数的和的绝对值)
方案数 = abs(正数的和-负数的和的绝对值) + 1
代码 :
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 100050;
int a[maxn];
typedef long long ll;
int main(){
cin.sync_with_stdio(false);
cin.tie(0);
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; //pos 正数和 neg 负数和
for(int i = 2 ; i <= n ; i++){
if(a[i] > 0 ) pos += a[i];
else neg -= a[i];
}
cout << min(pos , neg) + abs(pos - neg) <<endl;
cout << abs(pos - neg) + 1 <<endl;
return 0;
}
3. Tallest Cow (差分)
分析 :任意一对Ai , Bi不可能发生交叉
只可能:
问题转换成 : 一个序列所有元素都是 H 的序列 , 给的每一个关系A ,B把 (A, B)之间的元素都 -1 ,问每个数最终是多少
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <set>
#include <cstring>
using namespace std;
const int maxn = 100005;
int n , i , h , r , sum[maxn];
set<pair<int,int> > s;
int main(){
memset(sum , 0 , sizeof sum);
cin >> n >> i >> h >> r;
sum[1] = h;
for(int i = 0 ; i < r ; i ++){
int x , y;
cin >> x >> y;
if(x == y) continue;
if(x > y) swap(x , y);
if(s.find(make_pair(x,y))!=s.end()) continue;
s.insert(make_pair(x,y));
sum[x + 1] -= 1;
sum[y] += 1;
}
for(int i = 1 ; i <= n ; i ++) sum[i] += sum[i - 1];
for(int i = 1 ; i <= n ; i ++) cout << sum[i] <<endl;
}