前缀和 and 差分
前缀和
一维前缀和:
预处理: \(O(n)\)
\(S[i] = a[1] + a[2] + ... a[i]\)
求区间[L,R]的和:\(O(1)\)
\(a[ L ]+...+a[ R ] = S[r] - S[l - 1]\)
二维前缀和:.
预处理: \(O(nm)\)
S[i, j] = 第i行j列格子左上部分所有元素的和
求以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和: \(O(1)\)
\(S[x_2 , y_2 ]-S[x_1 -1 , y_2 ]-S[x_2 , y_1 -1]+S[x_1 -1 , y_1 -1]\)
模板题
P2280 [HNOI2003]激光炸弹
洛谷
acwing
题意:
地图上有 N 个目标,,每个目标都有一个价值 Wi。每个炸弹有个范围,求一颗炸弹最多能炸掉地图上总价值为多少的目标。
思路:
二分前缀和
代码:
const int N = 5010;
int s[N][N];
int n,r;
void solve()
{
cin>>n>>r;
r = min(r, 5001);
for(int i=0;i<n;i++){
int x,y,w;
cin>>x>>y>>w;
s[x+1][y+1]+=w;
}
//预处理前缀和
for(int i=1;i<=5001;i++)
for(int j=1;j<=5001;j++)
s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
int ans=0;
for(int i=r;i<=5001;i++)
for(int j=r;j<=5001;j++)
ans=max(ans,s[i][j]-s[i-r][j]-s[i][j-r]+s[i-r][j-r]);
cout<<ans<<endl;
}
差分
思路:
- 想要让\([l, r]\)上面整体加上\(c\),在一个长的数组上是无法实现单方面让\([l, r]\)上加
- 只有先让 \(l\) 右边的所有数先加上\(c\), 在让 \(r + 1\)右边的减去 \(c\), 这样就可以让\([l, r]\)上加上\(c\)了
代码:
int insert(int l, int r, int c)
{
b[l] += c;
b[r + 1] -= c;
}
应用
增减序列
洛谷
acwing
原文
题意:
给定一个长度为 n 的数列 \(a1,a2,…,an\),每次可以选择一个区间 \([l,r]\),使下标在这个区间内的数都加一或者都减一。
求
- 至少需要多少次操作才能使数列中的所有数都一样,
- 并求出在保证最少次数的前提下,最终得到的数列可能有多少种。
分析:
先求出差分数组
\(B[1]=A[1]\)
\(B[i]=A[i]−A[i−1]\) \((2<=i<=n)\)
很明显题意1在差分数组的体现就是:\(B_2 ... B_n\) 都为零,B1取值无所谓。
贪心:
- 我们可以,每一次选取\(B_i 和B_j\),\(2<=i,j<=n\),而且这两个数,一个为正数,一个为负数 。
为什么要是正负配对?因为我们是要这个B序列2~n都要为0,所以这样负数增加,正数减少,就可以最快地到达目标为0的状态。
- 至于那些无法配对的数\(B_k\)可以选\(B_1\)或者\(B_{n+1}\),这两个不影响的数,进行修改。
所以说最少操作数就是\(min(p,q)+abs(p−q)=max(p,q)\) 。
p为b序列中正数之和,而q为b序列中负数之和
我们知道经过操作后,数组的值就是b[1]的值。因此数组的情况就是b[1]可能的取值。然后我们贪心过程中,就只有第二步会改变b[1]的值,所以
最终序列a可能会有\(abs(p−q)+1\)种情况。
代码:
const int N = 100010;
typedef long long LL;
int n;
int a[N], b[N];
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
for (int i = 1; i <= n; i ++ ) b[i] = a[i] - a[i - 1];
LL p = 0, q = 0;
for (int i = 2; i <= n; i ++ )
if (b[i] > 0) p += b[i];
else q -= b[i];
cout << max(p, q) << endl;
cout << abs(p - q) + 1 << endl;
return 0;
}