一维差分小记

原理

通过求数组差,以将 [ l , r ] [l,r] [l,r] 区间里的数据全部加上 c c c
将查分数组进行前缀和以应用所有更改。

模板

/*核心代码*/
#include <iostream>
using namespace std;
int a[100010],ch[100010];
void add(int l,int r,int c)
{
	ch[l]+=c;
	ch[r+1]-=c;
	return ;
}
/*主函数参考*/
int n,m;
cin>>n>>m;
for (int i=1;i<=n;i++)
{
	cin>>a[i];
	ch[i]=a[i]-a[i-1];
}
for (int i=1;i<=m;i++)
{
	int l,r,c;
	cin>>l>>r>>c;
	add(l,r,c);
}
/*输出最终结果*/
for (int i=1;i<=n;i++)
	{
		ch[i]+=ch[i-1];
		cout<<ch[i]<<' ';
	}

e.g. 【模板】差分(Yogeek HOJ)

此题连同后面共三题全是模板应用。

模板题。

#include <iostream>
using namespace std;
int a[100010],ch[100010];
void add(int l,int r,int c)
{
	ch[l]+=c;
	ch[r+1]-=c;
	return ;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n,m;
	cin>>n>>m;
	for (int i=1;i<=n;i++)
	{
		cin>>a[i];
		ch[i]=a[i]-a[i-1];
	}
	for (int i=1;i<=m;i++)
	{
		int l,r,c;
		cin>>l>>r>>c;
		add(l,r,c);
	}
	for (int i=1;i<=n;i++)
	{
		ch[i]+=ch[i-1];
		cout<<ch[i]<<' ';
	}
	return 0;
}

e.g. 【基础】煎饼达人(Yogeek HOJ)

裸差分。
∵ 曰:(提示:可以用0表示煎饼的反面,1表示煎饼的正面)
∴ 本题连差分数组都不需要构建。(初始都是0)

#include <iostream>
#include <cmath>
using namespace std;
int a[1000010],ch[1000010];
void add(int l,int r)
{
	ch[l]+=1;
	ch[r+1]-=1;
	return ;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n,m,ans=0;
	cin>>n>>m;
	for (int i=1;i<=m;i++)
	{
		int l,r;
		cin>>l>>r;
		add(l,r);
	}
	for (int i=1;i<=n;i++)
	{
		ch[i]+=ch[i-1];
	}
	for (int i=1;i<=n;i++)
	{
		if (ch[i]%2!=0)
		{
			++ans;
		}
	}
	cout<<ans<<'\n';
	return 0;
}

e.g. [山东CSP-J2022T1] 植树节(Yogeek HOJ)

思路

还是裸差分。
需要注意的是,本题并没有给出树的编号范围。因此,需要自己判断。

#include <iostream>
#include <cmath>
using namespace std;
int a[1000010],ch[1000010];
void add(int l,int r)
{
	ch[l]+=1;
	ch[r+1]-=1;
	return ;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n,ml=1e9,mr=-1e9,maxx=-1e9;
	cin>>n;
	for (int i=1;i<=n;i++)
	{
		int l,r;
		cin>>l>>r;
		add(l,r);
		ml=min(ml,l);
		mr=max(mr,r);
	}
	for (int i=ml;i<=mr;i++)
	{
		ch[i]+=ch[i-1];
		maxx=max(maxx,ch[i]);
	}
	cout<<maxx<<'\n';
	return 0;
}

e.g.[NOIP2012提高组D2T2]借教室(Yogeek HOJ)

思路

这题就上难度了。

首先,先用一个简单的差分解决教室租借。用暴力的方法,差分完后枚举这段区间,发现不够直接return false

#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;
int a[1000010],ch[1000010],dh[1000010];
bool add(int l,int r,int c,int x)
{
	ch[l]+=(-c);
	ch[r+1]-=(-c);
	memset(dh,0,sizeof(dh));
	for (int i=l;i<=r;i++)
	{
		dh[i]=dh[i-1]+ch[i];
		if (dh[i]<0)
		{
			cout<<"-1\n"<<x<<'\n';
			return false;
		}
	}
	return true;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n,m;
	cin>>n>>m;
	for (int i=1;i<=n;i++)
	{
		cin>>a[i];
		ch[i]=a[i]-a[i-1];
	}
	for (int i=1;i<=m;i++)
	{
		int d,s,t;
		cin>>d>>s>>t;
		if (!add(s,t,d,i))
		{
			return 0;
		}
	}
	cout<<0<<'\n';
	return 0;
}

可是每一次都枚举子区间的时间复杂度把差分的优势完全抵消,甚至成为了DFS的时间复杂度,肯定挂了。

此时,该算法时间复杂度是 O ( n 2 ) O(n^{2}) O(n2) P . S . 我不知道对不对啊 \tiny{P.S.我不知道对不对啊} P.S.我不知道对不对啊,要想过此题,必须要有一个 O ( n log ⁡ n ) O(n \log{n}) O(nlogn) 的算法,第一个冒出来的就是二分

我们建立一个每日教室数量数组 a a a (同时也是差分数组),在执行完差分后直接找最大值 m a x max max m a x + 1 max+1 max+1就是最终不通过时的答案)。在二分函数 b i n a r y _ s e a r c h binary\_search binary_search中,判断时使用的 c h e c k check check函数是整体的难点。

首先memset数组(每次租借的初始化),随后将差分数组前缀和(前面说过对差分数组进行前缀和可以应用所有改变),直接枚举,判断每日需求量是否大于每日教室“储备量”。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
long long s[1000010],t[1000010],d[1000010],r[1000010],a[1000010];
int n,m;
void add(int l,int r,int c)
{
	a[l]+=c;
	a[r+1]-=c;
}
bool check(int x)
{
	memset(a,0,sizeof(a));
	for (int i=1;i<=x;i++)
	{
		add(s[i],t[i],d[i]);
	}
	for (int i=1;i<=n;i++)
	{
		a[i]+=a[i-1];
		if (a[i]>r[i])
		{
			return false;
		}
	}
	return true;
}
int binary_search()
{
	int l=0,r=m;
	while (l<r)
	{
		int mid=(l+r+1)>>1;
		if (check(mid))
		{
			l=mid;
		}
		else
		{
			r=mid-1;
		}
	}
	return l;
}
int main()
{
	typedef long long LL;
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n>>m;
	for (int i=1;i<=n;i++)
	{
		cin>>r[i];
	}
	for (int i=1;i<=m;i++)
	{
		cin>>d[i]>>s[i]>>t[i];
	}
	if (binary_search()>=m)
	{
		cout<<0<<'\n';
	}
	else
	{
		cout<<-1<<'\n'<<binary_search()+1<<'\n';
	}
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值