前缀和与差分
1、二维差分
一维差分时是在需要更新的位置开始,不需要的位置结束,而二维的也一样。如果我们要在左上角是 (x1, y1),右下角是(x2,y2)的矩形区间每个值都+a,我们要在区间开始位置(x1,y1)处+a,根据前缀和的性质,那么它影响的就是整个黄色部分,多影响了两个蓝色部分,所以在两个蓝色部分-a消除+a的影响,而两个蓝色部分重叠的绿色部分多受了个-a的影响,所以绿色部分+a消除影响。
2、二维差分入门练习—P3397 地毯
P3397 地毯 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
暴力可以做,复杂度n3勉强满足n,m <= 1000,使用二维前缀和和二维差分后,复杂度降至O(n2)。差分与前缀和互为逆运算,一维差分使用O(1)复杂度表示O(n)的覆盖,而二维差分使用O(1)的复杂度表示O(n2)的覆盖
#include<cstdio>
#include<iostream>
using namespace std;
long long m,n,a[1010][1010];
//直接模拟
void solve1(){
cin>>n>>m;
long long x1,y1,x2,y2;
for(long long i=0;i<m;i++){
cin>>x1>>y1>>x2>>y2;
for(long long j=x1;j<=x2;j++){
for(long long k=y1;k<=y2;k++) a[j][k]++;
}
}
for(long long i=1;i<=n;i++){
for(long long j=1;j<=n;j++) printf("%lld ",a[i][j]);
printf("\n");
}
}
//二维差分
void solve2(){
cin>>n>>m;
long long x1,x2,y1,y2;
for(long long i=1;i<=m;i++){
cin>>x1>>y1>>x2>>y2;
a[x1][y1]++;
a[x1][y2+1]--;
a[x2+1][y1]--;
a[x2+1][y2+1]++;
}
//逆向还原(求前缀和)
for(long long i=1;i<=n;i++){
for(long long j=1;j<=n;j++){
a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];
printf("%lld ",a[i][j]);
}
printf("\n");
}
}
int main()
{
solve2();
system("pause");
return 0;
}
3、一维差分练习—P3406 海底高铁
P3406 海底高铁 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
这道题是一维前缀和/一维差分,采用差分思想O(m)读入,使用前缀和获取覆盖并进行方案选择花费O(n)。总的时间复杂度O(m+n)可以把前缀和与选择方案合二为一
#include <iostream>
#include <cmath>
using namespace std;
long long n,m,A,B,C,cover[100100];
long long dist=0;
void solve1(){
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
scanf("%lld%lld",&n,&m);
long long startx, endx;
cin >> startx;
//一维差分输入
for(long long j = 2; j <= m; j++){
scanf("%lld",&endx);
if(startx < endx){
cover[startx]++;
cover[endx]--;
}
else{
cover[endx]++;
cover[startx]--;
}
//Swap(startx,endx);
startx = endx;
}
//前缀和求覆盖+选择
for(long long i = 1; i < n; i++){
cover[i] += cover[i - 1];
scanf("%lld%lld%lld",&A,&B,&C);
dist += A*cover[i] < B*cover[i] + C ? A*cover[i] : B*cover[i] + C;
}
}
int main()
{
solve1();
//system("pause");
cout<<dist<<endl;
return 0;
}
教训:
1、long long和int 不要混用,或许会带来精度的损失导致出错?这道题混用的记录如下记录详情 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
2、理解清楚题意再做,有些情况下没有AC并不是算法问题,可能是题目中的某一小步理解错误,比如这道题“从城市 P1出发分别按照P1,P2,P3…PM 的顺序访问各个城市”被我理解成“从城市1出发…”导致与最终结果总相差一个常数
3、长时间WR看答案后认为算法没问题就要仔细再读题,仔细看题解找不同。
4、差分思想优化—序列查询新解(CSP杂题)
注意到N在109量级,而n在105直接模拟会超时得到70分
思想:将0—N的模拟转化为0—n的等差数列求和。具体是按照这n个数进行分组,然后按照r个等差数列求和,最后减去多求的部分(代码中error -= fabs(first) * A_begin_rem + fabs(last) *A_end_rem)
#include <iostream>
#include <cmath>
using namespace std;
long long func(long long first, long long last, long long r)
{
long long result;
if (first * last >= 0)
{
result=fabs(first + last) * (fabs(last - first) + 1) / 2 * r;
}
else
{
result = (fabs(first) + 1) * fabs(first) / 2 * r + (last + 1) * last / 2 * r;
}
return result;
}
int main()
{
int n, N;
cin >> n >> N;
long long r = N / (n + 1);
long long A[n]={0};
for (int i = 1; i <= n; ++i)
{
cin >> A[i];
}
A[n+1]=N;
long long error = 0;
for (int i = 0; i <= n; ++i)
{
long long begin_num = A[i] / r;
long long end_num = (A[i + 1] - 1) / r;
long long first = begin_num - i, last = end_num - i;
error += func(first, last, r);
long long A_bebin_rem = A[i] % r;
long long A_end_rem = r - (A[i + 1] - 1) % r - 1;
error -= fabs(first) * A_begin_rem + fabs(last) * A_end_rem;
}
cout << error << endl;
//system("pause");
return 0;
}
教训:
1、中间变量命名规范,便于阅读
2、写程序之前可以先在纸上演算,确定思路正确再写代码
3、适当空格、空行使代码看起来清晰