原理
通过求数组差,以将
[
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;
}