查分數組
差分數組是通過維護相鄰元素的差值,來實現對多次區間操作的優化。(第一次見)
比方說給定多次操作 a,b,c, 對[a,b]內元素都+c 。然後求各元素的值
如果是普通的暴力求法,每次對[a,b]複雜度太高了。所以我們整一個優化的技巧。穿件一個差項數組。 。
因爲這樣我們發現的得到a[i]的式子
修改複雜度O(1),查詢複雜度O(n)。
參考資料:這個大佬的博客
https://www.cnblogs.com/COLIN-LIGHTNING/p/8436624.html
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
int d[100010],a[100010],l,r;
int main(){
int n;
while(scanf("%d",&n),n)
{
memset(d,0,sizeof(d));
memset(a,0,sizeof(a));
for(int i=1;i<=n;++i){
scanf("%d%d",&l,&r);
d[l]+=1;
d[r+1]-=1;
}
for(int i=1;i<=n;++i) a[i]=a[i-1]+d[i];
for(int i=1;i<n;++i) printf("%d ",a[i]);
printf("%d\n",a[n]);
}
return 0;
}
題目
https://www.luogu.org/problem/P1083
題意:中文題意,給定一個序列,進行多次區間修改,輸出第一次區間修改不能執行的序號(比方說已經刪除成0 了,不可再刪除了)
思路:二分+差分數組。 O(logn*n)
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
/* 用差分數組,O1處理,然後判斷
讓誰 修改的時候二分答案一下。
*/
const int maxn=1e6+500;
typedef long long ll;
int a[maxn];
int b[maxn];
int c[maxn];
ll num[maxn];
ll cf[maxn];
int m;
bool judge(int p){
memset(cf,0,sizeof(cf));
for(int i=1;i<=p;i++){
cf[b[i]]+=1ll*a[i];
cf[c[i]+1]-=1ll*a[i];
}
for(int i=1;i<=m;i++){
cf[i]+=cf[i-1];
//cout<<cf[i]<<"*"<<i<<" "<<p<<endl;
if(cf[i]>num[i])return true;
}
return false;
}
int main()
{ int n;
scanf("%d%d",&m,&n);
for(int i=1;i<=m;i++){
scanf("%lld",&num[i]);
}
for(int i=1;i<=n;i++){
scanf("%d%d%d",&a[i],&b[i],&c[i]);
}
if(!judge(n)){
puts("0");return 0;
}
puts("-1");
int r=n*2;
int l=0;
int ans=-1;
while(l<r){
int mid=(l+r)>>1;
if(judge(mid)){
r=mid;
ans=mid;
}
else{
l=mid+1;
}
}
printf("%d\n",ans);
return 0;
}
樹狀數組的區間修改
關於樹狀數組,我知道他是一個非常巧妙的利用計算機存儲數字的特性得到的類似一種固態二分的寫法。利用線段樹進行點更新,區間查詢,異常簡單。但是區間修改呢(線段樹的lazy區間修改也很巧妙)。
我們已經知道了利用差分數組來求多次修改後的a[i]$$
ans = a[1] + a[2] + a[3] +……+ a[q-1] + a[q]
sum=sigma(c,1) + sigma(c,2) + sigma(c,3) +…… + sigma(c, q-1 ) + sigma(c, q )
=c[1] + ( c[1] + c[2] ) + ( c[1] + c[2] + c[3] ) + …… + (c[1] + c[2]+……+ c[q-1] )+ ( c[1] + c[2]+……+ c[q] )
= q*c[1] + (q-1)*c[2] + (q-2)*c[3] +…… + c[q]
= q* (c[1]+c[2]+...+c[q]) - (0*c[1]+1*c[2]+...+(q-1)*c[q])
=(q+1) *(c[1]+c[2]+...+c[q])-(1*c[1]+2*c[2]+...+q*c[q])
所以我們需要維護兩個樹狀數組 一個是正常的查分數組。另一個是i*w(w爲修改項),修改四次,雖然修改的次數多了兩次,但是代碼非常好寫。
證明過程是這個大佬的源出處 https://blog.csdn.net/weixin_42557561/article/details/81781916
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=200005,maxq=200005;
int n,q;
int lowbit(int x)
{
return x&(-x);
}
long long delta[maxn]; //差分數組
long long deltai[maxn]; //delta*i
long long sum[maxn];//原始前綴和
void update(long long *c,int x,int y)
{
while(x<=n)
{
c[x]+=y;
x+=lowbit(x);
}
}
long long query(long long *c,int x)
{
long long ans=0;
while(x>0)
{
ans+=c[x];
x-=lowbit(x);
}
return ans;
}
int x[maxn];
int main()
{
cin>>n>>q;
for(int i=1;i<=n;i++)
{
//int x;
cin>>x[i];
//sum[i]=sum[i-1]+x;
}
for(int i=1;i<=n;i++){
update(delta,i,x[i]-x[i-1]);
update(deltai,i,(x[i]-x[i-1])*i);
}
while(q--)
{
int x;
cin>>x;
if(x==1)
{
int y,z,w;
cin>>y>>z>>w;
update(delta,y,w);
update(delta,z+1,-w);
update(deltai,y,w*y);
update(deltai,z+1,-w*(z+1));
}
if(x==2)
{
int y,z;
cin>>y>>z;
long long suml=(z+1)*query(delta,z)-query(deltai,z);
suml-=y*query(delta,y-1)-query(deltai,y-1);
cout<<suml<<endl;
}
}
return 0;
}
記:那倆示例代碼都是扒的emm,以後碰見相關思路的題目我在補到這上面吧。我第一次看見查分數組這種操作還以爲是codeforces的騷操作(畢竟codeforces上有很多我不理解的數學證明(智力碾壓)的結論)沒想到基本的思想竟然和我最喜歡的BIT有關。