Bootstrap

数据结构与算法:一维差分与等差数列差分

前言

差分和前缀和一样,也是很重要的基础算法。

一、一维差分

1.内容

当给出一个数组,每次操作让数组某个区间上的值全增加,最后要求返回整个数组的结果。若是一次一次去遍历,时间复杂度肯定很难看。差分可以做到在时间复杂度良好的情况下解决这一类问题。

注意,差分只能做到所有操作结束后返回结果,不能做到边操作边查询。

2.模板——航班预订统计

class Solution {
public:
    vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n) {
        vector<int>help(n+2,0);
        for(int i=0;i<bookings.size();i++)
        {
            help[bookings[i][0]]+=bookings[i][2];
            help[bookings[i][1]+1]-=bookings[i][2];
        }

        vector<int>ans(n,0);
        ans[0]=help[1];
        for(int i=1;i<n;i++)
        {
            ans[i]=ans[i-1]+help[i+1];
        }
        return ans;
    }
};

 分析题意会发现这个题就是差分的模板题。

一维差分的方法就是,当操作区间为(l,r)时,在数组l位置加上要增加的数值,数组r+1位置减去要增加的数值,最后构建前缀和数组即为答案。

二、等差数列差分

1.内容

和一维差分差不多,不一样的是每次操作在区间上每个位置增加的值构成一个等差数列。

2.模板——三步必杀

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

void solve()
{
	ll n;
	scanf("%lld",&n);
	ll m;
	scanf("%lld",&m);
	vector<vector<ll> >kill(m,vector<ll>(4,0));
	for(ll i=0;i<m;i++)
	{
		for(ll j=0;j<4;j++)
		{
			scanf("%lld",&kill[i][j]);
		}
	}
	
	vector<ll>ans(n+3,0);
	for(ll i=0,d;i<m;i++)
	{
		d=(kill[i][3]-kill[i][2])/(kill[i][1]-kill[i][0]);
		ans[kill[i][0]-1]+=kill[i][2];
		ans[kill[i][0]]+=d-kill[i][2];
		ans[kill[i][1]]-=d+kill[i][3];
		ans[kill[i][1]+1]+=kill[i][3];
	}
	
	for(int i=0;i<2;i++)
	{
		for(ll j=1;j<=n;j++)
		{
			ans[j]+=ans[j-1];
		}
	}
	
	ll max_num=0;
	ll eor=0;
	for(ll i=0;i<n;i++)
	{
		max_num=max(max_num,ans[i]);
		eor^=ans[i];
	}
	printf("%lld %lld",eor,max_num);
}

int main()
{
	solve();
	
	return 0;	
} 

 这个基本上就是等差数列差分的模板题。

等差数列差分的方法是,每次操作在数组l位置加上等差数列的首项,在l+1位置加上公差-首项,在r+1位置减去公差+末项,在r+2位置加上末项,最后构建两次前缀和数组即可。

3.练习——Lycanthropy

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const ll offset=30001;

void cal(ll l,ll r,ll s,ll e,ll d,vector<ll>&help)
{
	help[l+offset]+=s;
	help[l+1+offset]+=d-s;
	help[r+1+offset]-=d+e;
	help[r+2+offset]+=e;
}

void solve()
{
	ll n;
	scanf("%lld",&n);
	ll m;
	scanf("%lld",&m);
	vector<vector<int> >fall(n,vector<int>(2,0));
	for(ll i=0;i<n;i++)
	{
		for(ll j=0;j<2;j++)//0:v 1:loc 
		{
			scanf("%lld",&fall[i][j]);
		}
	}
	
	vector<ll>help(m+2*offset,0);
	for(ll i=0;i<n;i++)
	{
		cal(fall[i][1]-fall[i][0]+1,fall[i][1],-1,-fall[i][0],-1,help);
		cal(fall[i][1]+1,fall[i][1]+fall[i][0]-1,-fall[i][0]+1,-1,1,help);
		cal(fall[i][1]-2*fall[i][0],fall[i][1]-fall[i][0]-1,fall[i][0],1,-1,help);
		cal(fall[i][1]-3*fall[i][0]+1,fall[i][1]-2*fall[i][0]-1,1,fall[i][0]-1,1,help);
		cal(fall[i][1]+fall[i][0]+1,fall[i][1]+2*fall[i][0],1,fall[i][0],1,help);
		cal(fall[i][1]+2*fall[i][0]+1,fall[i][1]+3*fall[i][0]-1,fall[i][0]-1,1,-1,help);
	}
	
	for(int i=0;i<2;i++)
	{
		for(ll j=1;j<=m+offset;j++)
		{
			help[j]+=help[j-1];
		}
	}
	
	for(ll i=0;i<m;i++)
	{
		printf("%lld ",help[i+offset+1]);
	}
}

int main()
{
	solve();
	
	return 0;
}

 这个题看上去很复杂,但画个图分析一下会发现,每次落水后,左右两侧水位的变化呈等差数列,所以可以对其进行多次等差数列差分。

所有差分问题都有一个坑,就是数组下标越界的问题,所以每次准备时要准备大一点的辅助数组。而这个题又不一样,其下标可能会越界非常多,所以为了处理这个问题,考虑扩大数组大小,并最后用offset索引正确区间。

具体方法是,让答案数组的0位置对应辅助数组的offset+1位置。这里的offset为“溅起水花的最大长度”,用来处理在水池端点处落水的情况,所以在答案数组左右两侧分别加上一个offset长度。

还要注意的是,因为要求前缀和,所以不鞥只从offset+1位置开始,要从辅助数组开头开始,一直到m+offset位置。

总结

从最后一个题能看出,coding能力还要练啊!

END

;