差分好题.
判断数组相等可以用两数组做差解决. 而对于加一个斐波那契数列,可以用差分解决.
斐波那契数列通项公式为 \(f_i=f_{i-1}+f_{i-2}\),所以构造差分数组 \(d_i=c_i-c_{i-2}-c_{i-2}\).
修改的时候直接 \(\rm{O(1)}\) 修改就行了.
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mxn 300010
#define I inline
ll a[mxn],b[mxn],c[mxn],d[mxn];
ll fib[mxn],cnt,n,q,mod;
I void add(ll pos,ll x){
if(pos>n)return;x%=mod;
cnt-=(!d[pos]),d[pos]=((d[pos]+x)%mod+mod)%mod,cnt+=(!d[pos]);
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>q>>mod;
fib[1]=fib[2]=1;
for(ll i=3;i<=n;i++)fib[i]=(fib[i-1]+fib[i-2])%mod;
for(ll i=1;i<=n;i++)cin>>a[i];
for(ll i=1;i<=n;i++)cin>>b[i],c[i]=((a[i]-b[i])%mod+mod)%mod;
d[1]=c[1],cnt=(!d[1]);
for(ll i=2;i<=n;i++)d[i]=(c[i]-c[i-1]-c[i-2]+mod+mod)%mod,cnt+=(!d[i]);
for(ll i=1;i<=q;i++){
char c;ll l,r;cin>>c>>l>>r;
if(c=='A')add(l,1),add(r+1,-fib[r-l+1]-fib[r-l]),add(r+2,-fib[r-l+1]);
else add(l,-1),add(r+1,fib[r-l+1]+fib[r-l]),add(r+2,fib[r-l+1]);
if(cnt==n)cout<<"YES\n";
else cout<<"NO\n";
}
return 0;
}
这题直接三位偏序即可,但还有 \(\rm{O(n\log n)}\) 做法.
考虑 \(x+y\geq z\) 直接二位数点.
而 \(x+y<z\) 时,可以先数出 \(a+b\geq z\) 的的点,然后减去 \(a<x\) 和 \(b<y\) 的部分即可.
这样写比较麻烦,常数比较大,其实时间和 \(\rm{O(n\log^2n)}\) 差不多.
写三位偏序要注意的是,先处理点再处理询问,因为可能有点和询问相同的情况,若先处理询问则会把这个点漏掉.
#include<bits/stdc++.h>
using namespace std;
#define mxn 200010
#define lb lower_bound
#define I inline
#define ll long long
ll n,sze,q,ans[mxn];
ll X[mxn*3],c;
struct Tre{
ll tre[mxn];
I ll lowbit(ll x){return x&(-x);}
I void add(ll x,ll w){while(x<=c)tre[x]+=w,x+=lowbit(x);}
I ll get(ll x){ll ret=0;while(x)ret+=tre[x],x-=lowbit(x);return ret;}
}t;
struct Data{ll a,b,c,ans,id,opt;};
I void lis(Data& x){x.c=lb(X+1,X+c+1,x.c)-X;}
I bool cmpa(Data x,Data y){return x.a!=y.a?x.a<y.a:x.opt>y.opt;}
I bool cmpb(Data x,Data y){return x.b!=y.b?x.b<y.b:x.c<y.c;}
Data a[mxn];
I void cdq(ll l,ll r){
if(l==r)return;
ll mid=(l+r)>>1;
cdq(l,mid),cdq(mid+1,r);
sort(a+l,a+mid+1,cmpb),sort(a+mid+1,a+r+1,cmpb);
ll i,j=r;
for(i=mid;i>=l;i--){
while(a[j].b>=a[i].b&&j>mid){if(a[j].opt==0)t.add(a[j].c,1);j--;}
if(a[i].id>n)ans[a[i].id-n]+=t.get(c)-t.get(a[i].c-1);
}
for(ll i=r;i>j;i--)if(a[i].opt==0)t.add(a[i].c,-1);
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>q;
for(ll i=1;i<=n;i++)
cin>>a[i].a>>a[i].b,a[i].c=a[i].a+a[i].b,a[i].id=i,a[i].opt=0,
X[++c]=a[i].c;
for(ll i=1;i<=q;i++)
cin>>a[n+i].a>>a[n+i].b>>a[n+i].c,a[n+i].id=n+i,a[n+i].opt=1,
X[++c]=a[n+i].c;
sort(X+1,X+c+1);
c=unique(X+1,X+c+1)-X-1;
for(ll i=1;i<=n+q;i++)lis(a[i]);
sort(a+1,a+n+q+1,cmpa);
cdq(1,n+q);
for(ll i=1;i<=q;i++)cout<<ans[i]<<'\n';
return 0;
}
发现这题要求字典序最小的序列,这是一个很好的性质.
给修改序列(并非原序列)建差分数组,若该序列第一个不为 \(0\) 的数为负数,则字典序更小.
但这个方法只在第一次更新答案时有效. 每次更新后将整个序列清零就行了. 用线段树维护.
其实有用 \(\texttt{set}\) 的更简单的写法,不过我不会(懒得想了).
#include<bits/stdc++.h>
using namespace std;
#define mxn 500010
#define ll long long
#define I inline
struct Seg{ll lazy,nt0,sum;}tre[mxn<<2];
ll n,c[mxn],q,a[mxn],ans;
ll l[mxn],r[mxn],w[mxn];
I void push_up(int rot){
tre[rot].nt0=tre[rot<<1].nt0|tre[rot<<1|1].nt0;
}
I void push_down(int rot){
if(!tre[rot].lazy)return;
tre[rot]={0,0,0},tre[rot<<1]={1,0,0},tre[rot<<1|1]={1,0,0};
}
I void build(int rot,int l,int r){
tre[rot].sum=tre[rot].nt0=0;
if(l==r)return;
int mid=(l+r)>>1;
build(rot<<1,l,mid),build(rot<<1|1,mid+1,r);
}
I void modify1(int rot,int l,int r,int k,ll w){
if(l==r){
tre[rot].sum+=w;
tre[rot].nt0=(tre[rot].sum!=0);
return;
}
push_down(rot);int mid=(l+r)>>1;
if(k<=mid)modify1(rot<<1,l,mid,k,w);
else modify1(rot<<1|1,mid+1,r,k,w);
push_up(rot);
}
I int ask(int rot,int l,int r){
if(l==r)return tre[rot].sum<0;
push_down(rot);int mid=(l+r)>>1;
if(tre[rot<<1].nt0)return ask(rot<<1,l,mid);
else if(tre[rot<<1|1].nt0)return ask(rot<<1|1,mid+1,r);
return 0;
}
I void solve(){
cin>>n;for(int i=1;i<=n;i++)cin>>a[i];
build(1,1,n),ans=0;
for(int i=0;i<=n+1;i++)c[i]=0;
cin>>q;for(int i=1;i<=q;i++){
cin>>l[i]>>r[i]>>w[i];
modify1(1,1,n,l[i],w[i]);if(r[i]<n)
modify1(1,1,n,r[i]+1,-w[i]);
if(ask(1,1,n))ans=i,tre[1]={1,0,0};
}
for(int i=1;i<=n;i++)c[i]=a[i]-a[i-1];
for(int i=1;i<=ans;i++)
c[l[i]]+=w[i],c[r[i]+1]-=w[i];
ll Ans=0;
for(int i=1;i<=n;i++)Ans+=c[i],cout<<Ans<<' ';
cout<<'\n';
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int T;cin>>T;while(T--)solve();
return 0;
}
介绍一种简单(?的写法.
对于一个矩形 \(r_i\),怎么判断没有被覆盖呢?只要算出在其之后的矩形中与其无交的数量 \(num\).
若 \(num=n-i\) 则没有被覆盖.
计算与矩形 \(i\) 无交的矩形数量,可见此图.

我们可以算出严格在一条边外的矩形数量,再算出严格在一个角上的矩形数量,然后简单容斥就行了.
时间复杂度 \(\rm{O(n\log^2n)}\),因为要三位数点.
#include<iostream>
#include<algorithm>
using namespace std;
#define mxn 100010
#define pb push_back
#define I inline
#define lb lower_bound
int n,X[mxn<<2],c,ans[mxn];
struct Rec{int x1,y1,x2,y2,id;};
struct Tre{
int tre[mxn<<2];
I void init(){for(int i=1;i<=c;i++)tre[c]=0;}
I void add(int x,int w){while(x<=c)tre[x]+=w,x+=x&(-x);}
I int ask(int x){int ret=0;while(x)ret+=tre[x],x-=x&(-x);return ret;}
};
I bool cmpid(Rec x,Rec y){return x.id<y.id;}
I bool cmpx1(Rec x,Rec y){return x.x1<y.x1;}
I bool cmpx2(Rec x,Rec y){return x.x2<y.x2;}
I bool cmpy1(Rec x,Rec y){return x.y1<y.y1;}
I bool cmpy2(Rec x,Rec y){return x.y2<y.y2;}
Rec rec[mxn];
Tre Left,Right,Up,Down,t;
I void lis(Rec& x){
x.x1=lb(X+1,X+c+1,x.x1)-X,
x.x2=lb(X+1,X+c+1,x.x2)-X,
x.y1=lb(X+1,X+c+1,x.y1)-X,
x.y2=lb(X+1,X+c+1,x.y2)-X;
}
I void cdq1(int l,int r){
if(l==r)return;
int mid=(l+r)>>1;
cdq1(l,mid),cdq1(mid+1,r);
sort(rec+l,rec+mid+1,cmpy2),sort(rec+mid+1,rec+r+1,cmpy1);
int i,j=r;
for(i=mid;i>=l;ans[rec[i].id]-=t.ask(rec[i].x1),i--)
while(rec[j].y1>=rec[i].y2&&j>mid)t.add(rec[j].x2,1),j--;
for(j++;j<=r;j++)t.add(rec[j].x2,-1);
}
I void cdq2(int l,int r){
if(l==r)return;
int mid=(l+r)>>1;
cdq2(l,mid),cdq2(mid+1,r);
sort(rec+l,rec+mid+1,cmpy2),sort(rec+mid+1,rec+r+1,cmpy1);
int i,j=r;
for(i=mid;i>=l;ans[rec[i].id]-=t.ask(c)-t.ask(rec[i].x2-1),i--)
while(rec[j].y1>=rec[i].y2&&j>mid)t.add(rec[j].x1,1),j--;
for(j++;j<=r;j++)t.add(rec[j].x1,-1);
}
I void cdq3(int l,int r){
if(l==r)return;
int mid=(l+r)>>1;
cdq3(l,mid),cdq3(mid+1,r);
sort(rec+l,rec+mid+1,cmpy1),sort(rec+mid+1,rec+r+1,cmpy2);
int i,j=mid+1;
for(i=l;i<=mid;ans[rec[i].id]-=t.ask(rec[i].x1),i++)
while(rec[j].y2<=rec[i].y1&&j<=r)t.add(rec[j].x2,1),j++;
for(j--;j>mid;j--)t.add(rec[j].x2,-1);
}
I void cdq4(int l,int r){
if(l==r)return;
int mid=(l+r)>>1;
cdq4(l,mid),cdq4(mid+1,r);
sort(rec+l,rec+mid+1,cmpx2),sort(rec+mid+1,rec+r+1,cmpx1);
int i,j=r;
for(int i=mid;i>=l;ans[rec[i].id]-=t.ask(rec[i].y1),i--)
while(rec[j].x1>=rec[i].x2&&j>mid)t.add(rec[j].y2,1),j--;
for(j++;j<=r;j++)t.add(rec[j].y2,-1);
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
int x,y,a,b;cin>>x>>y>>a>>b;
rec[i]={x,y,x+a,y+b,i},
X[++c]=x,X[++c]=y,X[++c]=x+a,X[++c]=y+b;
}
sort(X+1,X+c+1),c=unique(X+1,X+c+1)-X-1;
for(int i=1;i<=n;i++)lis(rec[i]);
for(int i=n;i;i--){
ans[i]+=Left.ask(rec[i].x1)+Down.ask(rec[i].y1)+
Right.ask(c)-Right.ask(rec[i].x2-1)+
Up.ask(c)-Up.ask(rec[i].y2-1);
Left.add(rec[i].x2,1),Right.add(rec[i].x1,1),
Up.add(rec[i].y1,1),Down.add(rec[i].y2,1);
}
cdq1(1,n),sort(rec+1,rec+n+1,cmpid);
cdq2(1,n),sort(rec+1,rec+n+1,cmpid);
cdq3(1,n),sort(rec+1,rec+n+1,cmpid);
cdq4(1,n);
for(int i=1;i<=n;i++)
if(ans[i]!=n-i)cout<<"NE\n";
else cout<<"DA\n";
return 0;
}

假设 \(f_{i,j}\) 为第一张牌为 \(i\),第二张牌为 \(j\) 时可选牌的数量,初始时 \(f_{i,j}=mxc.\)
现在我们有一张牌 \((a,b,c)\),容易得到:
- 对于 \(i\in [1,a],j\in [1,b],f_{i,j}=0\).
- 对于 \(i\in [a+1,mxa],j\in [1,b],f_{i,j}=\min(f_{i,j},mxc-c)\).
- 对于 \(i\in [1,a],j\in [b+1,mxb],f_{i,j}=\min(f_{i,j},mxc-c)\).
转化一下,就是先让前 \(i\) 行和前 \(j\) 列对 \(mxc-c\) 取 \(\min\),然后将矩形 \([a,b]\) 填上零.
具体维护就是每次对一张卡牌,\(x_a=\min(x_a,a),y_b=\min(y_b,b),z_a=\max(z_a,b)\),然后对 \(x\) 和 \(y\) 取后缀 \(\min\),对 \(z\) 取后缀 \(\max\).
对于 \((i,j)\),若 \(z_i\geq j\),则 \(f_{i,j}=0\);否则 \(f_{i,j}=\min(a_i,b_j)\).
但是这样做是 \(\rm{O(n^2)}\) 的,会超时. 怎么办呢?
容易发现 \(x\) 和 \(y\) 这两个数组是单调递增的,所以可以维护一个指针 \(pos\) 满足对于 \(j\in [1,pos-1]\),\(x_i\geq y_j\) 且对于 \(j\in [pos,mxb],x_i<y_j\). 可以发现从第一行到第 \(mxa\) 行,这个 \(pos\) 也是单调递增的.
所以我们可以维护 \(x\) 的前缀和,加上 \(x_i\times (mxb-pos+1)\) 就行了,但是填零怎么处理呢,让 \(k=\max(pos,z_i+1)\),统计答案时用 \(k\) 统计.
#include<bits/stdc++.h>
using namespace std;
#define mxn 500010
#define ll long long
ll x[mxn],y[mxn],z[mxn],n,ans;
ll sum[mxn],xmx,ymx,zmx;
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>xmx>>ymx>>zmx;
for(int i=1;i<=xmx;i++)x[i]=zmx;
for(int i=1;i<=ymx;i++)y[i]=zmx;
for(int i=1;i<=n;i++){
ll X,Y,Z;cin>>X>>Y>>Z;
x[X]=min(x[X],zmx-Z),y[Y]=min(y[Y],zmx-Z),z[X]=max(z[X],Y);
}
for(int i=xmx-1;i;i--)x[i]=min(x[i],x[i+1]),z[i]=max(z[i],z[i+1]);
for(int i=ymx-1;i;i--)y[i]=min(y[i],y[i+1]);
for(int i=1;i<=ymx;i++)sum[i]=sum[i-1]+y[i];
ll pos=1;
for(int i=1;i<=xmx;i++){
while(x[i]>=y[pos]&&pos<=ymx)pos++;
ll k=max(pos,z[i]+1);
ans+=sum[k-1]-sum[z[i]]+(ymx-k+1)*x[i];
}
cout<<ans;
return 0;
}
上面那道题的四维版本.
假设 \(f_{i,j,k}\) 为第一张牌为 \(i\),第二张牌为 \(j\) ,第三张牌为 \(k\) 时可选牌的数量,初始时 \(f_{i,j,k}=mx.\)
可以像上一题一样考虑加一张牌 \((a,b,c,d)\) 带来的影响:
- 将 \((0,0,0) - (a,b,n)\) 这个长方体填上零.
- 将 \((0,0,0) - (a,n,c)\) 这个长方体填上零.
- 将 \((0,0,0) - (n,b,c)\) 这个长方体填上零.
- 将 \(0\sim c\) 层对 \(mx-d\) 取 \(\min\).
- 将 \(0\sim b\) 列对 \(mx-d\) 取 \(\min\).
- 将 \(0\sim a\) 行对 \(mx-d\) 取 \(\min\).
我们算出对于每一行,取零的最大列 \(x_i\),以及对于每一层,取零的最大行 \(y_k\) 和取零的最大列 \(z_k\).
对于一个点 \((i,j,k)\),若 \(j\leq x_i,i\leq y_k,j\leq z_k\),则 \(f_{i,j,k}=0\),否则 \(f_{i,j,k}=\min(a_i,b_j,c_k)\).
看起来更加复杂,我们怎么快速维护求值呢?
先一层一层求. 在上一题中,对于每一行,我们求出了第一个满足 \(a_i<b_j\) 的 \(i\).
这次我们把第一个 \(b_j> a_i\) 的列,第一个 \(c_k<a_i\) 的行以及第一个 \(c_k<b_j\) 的列求出来,记作数组 \(f,g,h\).
我们维护被 \(b_j\) 覆盖时,\(b_j\) 的和以及 \(a_i\) 的和,以及被 \(c_k\) 覆盖时, \(c_k\) 的和以及 \(a_i\) 的和,具体过程如下:
从第 \(i-1\) 层到第 \(i\) 层,一定会有 \(y_{i-1}-y_i\) 行取消填零,然后我们对这些行做处理.
对于第 \(j\) 行,做贡献的肯定是 \(k\in (x_j+1,mx]\) 列,首先维护 \(x_j+1\sim f_j-1\) 中 \(b_i\) 的和,然后再维护 \(f_j\sim mx\) 中 \(a_j\) 的和.
最后就是维护被 \(c_i\) 覆盖的部分,具体如下:
- 若 \(j<g_{i-1}\),则要维护 \(x_j+1\sim mx\) 中 \(a_j\) 的和,否则维护 \(c_i\) 的和,这一部分在上面处理取消填零的时候顺带做处理.
- 对于 \(j\in [g_{i-1},g_i)\) 这些行,我们单独处理. 若 \(j>y_i\),则维护 \(x_j+1\sim mx\) 中 \(a_j\) 的和,然后取消 \(c_i\) 的覆盖.
最后统计答案的时候,让这些列按是被 \(b_j\) 覆盖还是被 \(c_i\) 覆盖,然后分别求和.
整个过程需要用到四个数据结构维护,维护 \(b_i\) 的和时因为其特殊性(区间加时权值不同),所以用线段树;
维护其他值时可以用区间修改区间查询的树状数组维护,常数较小.
#include<bits/stdc++.h>
using namespace std;
#define mxn 500010
#define int unsigned int
#define I inline
int a[mxn],b[mxn],c[mxn];
int x[mxn],y[mxn],z[mxn];
int f[mxn],g[mxn],h[mxn];
int n,m,ans;
struct Tre{
int tre1[mxn],tre2[mxn];
I int lowbit(int x){return x&(-x);}
I void add(int x,int w){int tmp=x;while(x<=n)tre1[x]+=w,tre2[x]+=tmp*w,x+=lowbit(x);}
I int query(int x){int ret=0,tmp=x;while(x)ret+=(tmp+1)*tre1[x]-tre2[x],x-=lowbit(x);return ret;}
I void Add(int l,int r,int k){if(l>r)return;add(l,k),add(r+1,-k);}
I int Query(int l,int r){if(l>r)return 0;return query(r)-query(l-1);}
};
struct Seg{
int tre[mxn<<2],lazy[mxn<<2],val[mxn]={0};
I void push_down(int rot,int l,int r){
int mid=(l+r)>>1;
tre[rot<<1]+=lazy[rot]*(val[mid]-val[l-1]),lazy[rot<<1]+=lazy[rot];
tre[rot<<1|1]+=lazy[rot]*(val[r]-val[mid]),lazy[rot<<1|1]+=lazy[rot];
lazy[rot]=0;
}
I void push_up(int rot){tre[rot]=tre[rot<<1]+tre[rot<<1|1];}
I void add(int rot,int l,int r,int x,int y,int k){
if(r<x||l>y||l>r)return;
if(l>=x&&r<=y){lazy[rot]+=k,tre[rot]+=k*(val[r]-val[l-1]);return;}
push_down(rot,l,r);
int mid=(l+r)>>1;
add(rot<<1,l,mid,x,y,k),add(rot<<1|1,mid+1,r,x,y,k);
push_up(rot);
}
I int query(int rot,int l,int r,int x,int y){
if(r<x||l>y||l>r)return 0;
if(l>=x&&r<=y)return tre[rot];
push_down(rot,l,r);
int mid=(l+r)>>1;
return query(rot<<1,l,mid,x,y)+query(rot<<1|1,mid+1,r,x,y);
}
};
Seg ds1;Tre ds2,ds3,ds4;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)a[i]=b[i]=c[i]=n;
for(int i=1;i<=m;i++){
int A,B,C,D;cin>>A>>B>>C>>D;
a[A]=min(a[A],n-D),b[B]=min(b[B],n-D),c[C]=min(c[C],n-D);
x[A]=max(x[A],B),y[C]=max(y[C],A),z[C]=max(z[C],B);
}
for(int i=n-1;i;i--)
a[i]=min(a[i],a[i+1]),b[i]=min(b[i],b[i+1]),c[i]=min(c[i],c[i+1]),
x[i]=max(x[i],x[i+1]),y[i]=max(y[i],y[i+1]),z[i]=max(z[i],z[i+1]);
int F=1,G=1,H=1;
for(int i=1;i<=n;i++){
while(b[F]<=a[i]&&F<=n)F++;
while(a[G]<=c[i]&&G<=n)G++;
while(b[H]<=c[i]&&H<=n)H++;
f[i]=F,g[i]=G,h[i]=H;
}
y[0]=n,g[0]=1;
for(int i=1;i<=n;i++)ds1.val[i]=ds1.val[i-1]+b[i];
for(int i=1;i<=n;i++){
for(int j=y[i-1];j>y[i];j--){
int l=x[j]+1,r=max(l-1,f[j]-1);
ds1.add(1,1,n,l,r,1),ds2.Add(r+1,n,a[j]);
if(j<g[i-1])ds3.Add(l,n,a[j]);else ds4.Add(l,n,1);
}
for(int j=g[i-1];j<g[i];j++){
int l=x[j]+1;
if(j>y[i])ds3.Add(l,n,a[j]),ds4.Add(l,n,-1);
}
int l=z[i]+1,r=max(l-1,h[i]-1);
ans+=ds1.query(1,1,n,l,r)+ds2.Query(l,r)+ds3.Query(r+1,n)+ds4.Query(r+1,n)*c[i];
}
cout<<ans;
return 0;
}
见区间推平识 \(\rm{ODT}\).
对于每一种颜色都开一个 \(set\),对一个极长连续空段 \([l,r]\),其对答案的贡献为 \(-(r-l-k+2)\).
我们可以在第一位加长度 \(x\),往后 \(x\) 位每一位加 \(-1\),然后对于一个询问 \(k\),查询 \([1,k]\) 范围内的和就行了.
把这些空段的贡献扔到一个线段树上维护,空段用 \(\rm{ODT}\) 维护就行了.
#include<bits/stdc++.h>
using namespace std;
#define mxn 100010
#define ll long long
#define I inline
#define mp make_pair
#define lb lower_bound
#define ub upper_bound
ll n,q,a[mxn];
ll tmp[mxn<<1],c;
struct node{ll l,r,c;};
struct Ask{ll opt,l,r,x;};
bool operator<(const node& x,const node& y){return x.l<y.l;}
set<node> cth,col[mxn<<1];
Ask ask[mxn];
struct Seg{
ll tre[mxn<<2],lazy[mxn<<2];
I void push_up(int rot){tre[rot]=tre[rot<<1]+tre[rot<<1|1];}
I void push_down(int rot,int l,int r){
if(!lazy[rot])return;
int mid=(l+r)>>1;
tre[rot<<1]+=lazy[rot]*(mid-l+1),tre[rot<<1|1]+=lazy[rot]*(r-mid);
lazy[rot<<1]+=lazy[rot],lazy[rot<<1|1]+=lazy[rot],lazy[rot]=0;
}
I void modify(int rot,int l,int r,int x,int y,ll k){
if(r<x||l>y)return;
if(l>=x&&r<=y)return tre[rot]+=(r-l+1)*k,lazy[rot]+=k,void();
push_down(rot,l,r);
int mid=(l+r)>>1;
modify(rot<<1,l,mid,x,y,k),modify(rot<<1|1,mid+1,r,x,y,k);
push_up(rot);
}
I ll query(int rot,int l,int r,int x,int y){
if(r<x||l>y)return 0;
if(l>=x&&r<=y)return tre[rot];
push_down(rot,l,r);
int mid=(l+r)>>1;
return query(rot<<1,l,mid,x,y)+query(rot<<1|1,mid+1,r,x,y);
}
I void modify(int l,int r,ll w){modify(1,1,n,l,r,w);}
I ll query(int l,int r){return query(1,1,n,l,r);}
};
Seg s1;
I void Add(ll x,ll w){
if(x<1)return;
s1.modify(1,1,x*w),s1.modify(2,min(x+1,n),-w);
}
I void insert(node x){
auto itl=--col[x.c].lb(x),itr=col[x.c].ub(x);
int l=itl->r,r=itr->l;col[x.c].insert(x);
Add(r-l-1,-1),Add(x.l-l-1,1),Add(r-x.r-1,1);
}
I void del(node x){
auto itl=--col[x.c].lb(x),itr=col[x.c].ub(x);
int l=itl->r,r=itr->l;col[x.c].erase(x);
Add(r-l-1,1),Add(x.l-l-1,-1),Add(r-x.r-1,-1);
}
I auto split(ll pos){
auto it=cth.lb({pos,0,0});
if((*it).l==pos)return it;
node p=(*--it);
cth.erase(it),col[p.c].erase(p);
col[p.c].insert({p.l,pos-1,p.c}),col[p.c].insert({pos,p.r,p.c});
cth.insert({p.l,pos-1,p.c});
return cth.insert({pos,p.r,p.c}).first;
}
I void assign(ll l,ll r,ll cl){
auto itr=split(r+1),itl=split(l);
for(auto it=itl;it!=itr;it++)del(*it);
cth.erase(itl,itr);
cth.insert({l,r,cl}),insert({l,r,cl});
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>q;
cth.insert({0,0,0}),cth.insert({n+1,n+1,0});
for(int i=1;i<=n;i++)cin>>a[i],tmp[++c]=a[i];
for(int i=1;i<=q;i++){
cin>>ask[i].opt;
if(ask[i].opt==1)cin>>ask[i].l>>ask[i].r>>ask[i].x,tmp[++c]=ask[i].x;
else cin>>ask[i].x;
}
sort(tmp+1,tmp+c+1),c=unique(tmp+1,tmp+c+1)-tmp-1;
for(int i=1;i<=c;i++){
col[i].insert({0,0,i}),col[i].insert({n+1,n+1,i});
Add(n,1);
}
for(int i=1;i<=n;i++){
a[i]=lb(tmp+1,tmp+c+1,a[i])-tmp;
insert({i,i,a[i]}),cth.insert({i,i,a[i]});
}
for(int i=1;i<=q;i++){
if(ask[i].opt==1)ask[i].x=lb(tmp+1,tmp+c+1,ask[i].x)-tmp,assign(ask[i].l,ask[i].r,ask[i].x);
else cout<<c*(n-ask[i].x+1)-s1.query(1,ask[i].x)<<'\n';
}
return 0;
}
将查询区间从左到右扫描线,用一个树状数组记录每个时刻的值,区间推平时肯定有区间被覆盖,减去区间对应时刻的值.
最后用树状数组区间查询就行了.
#include<bits/stdc++.h>
using namespace std;
#define mxn 500010
#define I inline
#define ll long long
#define pii pair<ll,ll>
#define mp make_pair
#define pb push_back
#define lb lower_bound
#define X first
#define Y second
ll n,m,q,ans[mxn];
struct node{ll l,r,t,k;};
struct Tre{
ll tre[mxn]={0};
I ll lowbit(ll x){return x&(-x);}
I void add(ll x,ll w){if(x<1)return;while(x<=n)tre[x]+=w,x+=lowbit(x);}
I ll query(ll x){ll ret=0;while(x)ret+=tre[x],x-=lowbit(x);return ret;}
};
bool operator<(const node& a,const node& b){return a.l<b.l;}
Tre tre;node c[mxn];
vector<pii> ask[mxn];
set<node> cth;
I auto split(ll pos){
auto it=cth.lb({pos,0,0,0});
if(it->l==pos)return it;
it--;
ll l=(it->l),r=(it->r),t=(it->t),k=(it->k);
cth.erase(it),cth.insert({l,pos-1,t,k});
return cth.insert({pos,r,t,k}).X;
}
I void assign(node x){
auto itr=split(x.r+1),itl=split(x.l);
for(auto it=itl;it!=itr;it++){
ll l=(it->l),r=(it->r),t=(it->t),k=(it->k);
tre.add(t,-(r-l+1)*k);
}
cth.erase(itl,itr),cth.insert(x);
tre.add(x.t,(x.r-x.l+1)*x.k);
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m>>q;
cth.insert({1,m,0,0});
for(int i=1;i<=n;i++)cin>>c[i].l>>c[i].r>>c[i].k,c[i].t=i;
for(ll i=1;i<=q;i++){ll x,y;cin>>x>>y;ask[y].pb(mp(x,i));}
for(int i=1;i<=n;i++){
assign(c[i]);
for(pii j:ask[i])ans[j.Y]=tre.query(i)-tre.query(j.X-1);
}
for(int i=1;i<=q;i++)cout<<ans[i]<<'\n';
return 0;
}
对于整棵线段树,维护区间最大值,区间和(\(\&\))以及区间或(\(|\)).
若操作对整个区间的影响一致(或没有影响),则不继续修改.
具体到实现就是和操作对区间的或无影响,或者或操作对区间的和无影响,或者操作对区间和或影响相同,就不继续修改.
时间复杂度 \(\rm{O(n\log^2n)}\),可以用颜色段均摊分析.
#include<bits/stdc++.h>
using namespace std;
#define mxn 200010
#define I inline
struct Seg{
int tre[mxn<<2],lazy[mxn<<2],And[mxn<<2],Or[mxn<<2];
I void push_up(int rot){
tre[rot]=max(tre[rot<<1],tre[rot<<1|1]),
And[rot]=And[rot<<1]&And[rot<<1|1],
Or[rot]=Or[rot<<1]|Or[rot<<1|1];
}
I void push_down(int rot){
if(!lazy[rot])return;
int k=lazy[rot];lazy[rot]=0;
tre[rot<<1]+=k,tre[rot<<1|1]+=k,
Or[rot<<1]+=k,Or[rot<<1|1]+=k,
And[rot<<1]+=k,And[rot<<1|1]+=k,
lazy[rot<<1]+=k,lazy[rot<<1|1]+=k;
}
I void build(int rot,int l,int r){
if(l==r)return cin>>tre[rot],And[rot]=Or[rot]=tre[rot],void();
int mid=(l+r)>>1;
build(rot<<1,l,mid),build(rot<<1|1,mid+1,r);
push_up(rot);
}
I void modifyA(int rot,int l,int r,int x,int y,int k){
if(r<x||l>y)return;
if((Or[rot]&k)==Or[rot])return;
int ch1=(Or[rot]&k)-Or[rot],ch2=(And[rot]&k)-And[rot];
if(l>=x&&r<=y&&ch1==ch2)return tre[rot]+=ch1,And[rot]+=ch1,Or[rot]+=ch1,lazy[rot]+=ch1,void();
push_down(rot);
int mid=(l+r)>>1;
modifyA(rot<<1,l,mid,x,y,k),modifyA(rot<<1|1,mid+1,r,x,y,k);
push_up(rot);
}
I void modifyO(int rot,int l,int r,int x,int y,int k){
if(r<x||l>y)return;
if((And[rot]|k)==And[rot])return;
int ch1=(Or[rot]|k)-Or[rot],ch2=(And[rot]|k)-And[rot];
if(l>=x&&r<=y&&ch1==ch2)return tre[rot]+=ch1,And[rot]+=ch1,Or[rot]+=ch1,lazy[rot]+=ch1,void();
push_down(rot);
int mid=(l+r)>>1;
modifyO(rot<<1,l,mid,x,y,k),modifyO(rot<<1|1,mid+1,r,x,y,k);
push_up(rot);
}
I int query(int rot,int l,int r,int x,int y){
if(r<x||l>y)return 0;
if(l>=x&&r<=y)return tre[rot];
push_down(rot);
int mid=(l+r)>>1;
return max(query(rot<<1,l,mid,x,y),query(rot<<1|1,mid+1,r,x,y));
}
}s1;
int n,q;
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>q;
s1.build(1,1,n);
while(q--){
int opt,l,r,x;cin>>opt;
if(opt==1)cin>>l>>r>>x,s1.modifyA(1,1,n,l,r,x);
else if(opt==2)cin>>l>>r>>x,s1.modifyO(1,1,n,l,r,x);
else cin>>l>>r,cout<<s1.query(1,1,n,l,r)<<'\n';
}
return 0;
}
我们把一个区间分成 \(3\) 份,在前三分之一被切掉的一定是前三分之一,后三分之一同理.
然后找到最先切掉中间二分之一的操作,直接维护即可. 因为每次维护都切掉至少三分之一,所以时间复杂度带 \(\log\).
#include<bits/stdc++.h>
using namespace std;
#define mxn 1000010
#define I inline
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
#define X first
#define Y second
int n,m,l[mxn],r[mxn];
int Midt,Midp,tim[mxn];
vector<pii> t[mxn];
struct Seg{
int tre[mxn<<2],pos[mxn<<2];
I void push_up(int rot){
if(tre[rot<<1]>tre[rot<<1|1])tre[rot]=tre[rot<<1|1],pos[rot]=pos[rot<<1|1];
else tre[rot]=tre[rot<<1],pos[rot]=pos[rot<<1];
}
I void build(int rot,int l,int r){
if(l==r)return pos[rot]=l,tre[rot]=1e9,void();
int mid=(l+r)>>1;
build(rot<<1,l,mid),build(rot<<1|1,mid+1,r);
push_up(rot);
}
I void modify(int rot,int l,int r,int x,int k){
if(l==r)return tre[rot]=k,void();
int mid=(l+r)>>1;
if(x<=mid)modify(rot<<1,l,mid,x,k);
else modify(rot<<1|1,mid+1,r,x,k);
push_up(rot);
}
I void query(int rot,int l,int r,int x,int y){
if(r<x||l>y)return;
if(l>=x&&r<=y){if(tre[rot]<Midt)Midt=tre[rot],Midp=pos[rot];return;}
int mid=(l+r)>>1;
query(rot<<1,l,mid,x,y),query(rot<<1|1,mid+1,r,x,y);
}
I int searchL(int rot,int l,int r,int x,int k){
if(l>=x||tre[rot]>=k)return -1;
if(l==r)return l;
int mid=(l+r)>>1,ret=-1;
ret=max(ret,searchL(rot<<1|1,mid+1,r,x,k));if(ret>-1)return ret;
return searchL(rot<<1,l,mid,x,k);
}
I int searchR(int rot,int l,int r,int y,int k){
if(r<=y||tre[rot]>=k)return 1e9;
if(l==r)return l;
int mid=(l+r)>>1,ret=1e9;
ret=min(ret,searchR(rot<<1,l,mid,y,k));if(ret<1e9)return ret;
return searchR(rot<<1|1,mid+1,r,y,k);
}
};
Seg s1;
int id;
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m>>id;
s1.build(1,1,m);
for(int i=1;i<=n;i++)cin>>l[i]>>r[i];
for(int i=1;i<=m;i++){
int x,y,k;cin>>k>>x>>y,tim[k]=i;
t[x].pb(mp(k,1)),t[y+1].pb(mp(k,-1));
}
for(int i=1;i<=n;i++){
for(pii j:t[i])
if(j.Y>0)s1.modify(1,1,m,j.X,tim[j.X]);
else s1.modify(1,1,m,j.X,1e9);
while(1){
int len=max((r[i]-l[i]+1)/3,1),midl=l[i]+len,midr=r[i]-len;
Midt=1e9,s1.query(1,1,m,midl,midr);
l[i]=max(l[i],s1.searchL(1,1,m,midl,Midt));
r[i]=min(r[i],s1.searchR(1,1,m,midr,Midt));
if(Midt==1e9)break;
if(Midp-l[i]>=r[i]-Midp)r[i]=Midp;
else l[i]=Midp;
}
cout<<l[i]<<' '<<r[i]<<'\n';
}
return 0;
}
直接开 \(21\) 个树状数组,修改的时候在对应的 \(d\) 上差分.
询问的时候不断往上跳到 \(d\) 级祖先,询问对应子树,用树状数组和 \(\texttt{dfs}\) 序维护.
但对于 \(\texttt{lca(x,y)}\) 向上的这一部分很难处理,设 \(dp_{i,j}\) 为以 \(i\) 为根距离为 \(j\) 时的贡献,从 \(\texttt{lca}\) 开始暴力向上维护即可,注意要去重. 询问时也要加上 \(dp\) 数组的贡献.
#include<bits/stdc++.h>
using namespace std;
#define mxn 200010
#define I inline
#define pb push_back
int n,q;
int son[mxn],sze[mxn],top[mxn],fa[mxn],dep[mxn];
int in[mxn],out[mxn],cnt,dp[mxn][25];
vector<int> t[mxn];
struct Tre{
int tre[mxn]={0};
I int lowbit(int x){return x&(-x);}
I void add(int x,int w){if(!x)return;while(x<=n)tre[x]+=w,x+=lowbit(x);}
I int ask(int x){int ret=0;while(x)ret+=tre[x],x-=lowbit(x);return ret;}
};
Tre tre[21];
I void dfs1(int u,int f){
sze[u]=1,fa[u]=f,dep[u]=dep[f]+1;
for(int v:t[u]){
if(v==f)continue;
dfs1(v,u);
sze[u]+=sze[v];
if(sze[v]>sze[son[u]])son[u]=v;
}
}
I void dfs2(int u,int Top){
in[u]=++cnt,top[u]=Top;
if(son[u])dfs2(son[u],Top);
for(int v:t[u]){
if(v==fa[u]||v==son[u])continue;
dfs2(v,v);
}
out[u]=cnt;
}
I int lca(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]])swap(u,v);
u=fa[top[u]];
}
if(dep[u]<dep[v])return u;
else return v;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<n;i++){
int u,v;cin>>u>>v;
t[u].pb(v),t[v].pb(u);
}
dfs1(1,0),dfs2(1,1),cin>>q;
while(q--){
int opt;cin>>opt;
if(opt==1){
int x,ans=0;cin>>x;
int u=x;
for(int i=0;i<=20;i++){
ans+=tre[i].ask(out[u])-tre[i].ask(in[u]-1);
for(int j=i;j<=20;j++)ans+=dp[u][j];
if(!fa[u])break;u=fa[u];
}
cout<<ans<<'\n';
}
else{
int u,v,k,d;cin>>u>>v>>k>>d;
int w=lca(u,v);
tre[d].add(in[u],k),tre[d].add(in[v],k),tre[d].add(in[w],-2*k);
for(int i=d;i>=0;i--){
dp[w][i]+=k;
if(i>=2&&fa[w])dp[w][i-2]-=k;
if(!fa[w])break;w=fa[w];
}
}
}
return 0;
}
非常哈希的一道题.
我们怎么判断路径上的一种颜色的点数量是否相同呢?
随机出每种颜色的权值,然后处理出每个点到根的路径的信息,用主席树维护.
询问的时候利用树上差分的思想就行了.
#include<bits/stdc++.h>
using namespace std;
#define mxn 100000
#define ull unsigned long long
#define pb push_back
#define I inline
mt19937 rnd(time(0));
ull hs1[mxn+10],hs2[mxn+10];
int n,cnt,a[mxn+10],rt[mxn+10];
int sze[mxn+10],fa[mxn+10],dep[mxn+10],top[mxn+10],son[mxn+10];
int q,k,acnt,ans[mxn+10];
vector<int> t[mxn+10];
struct Seg{int ls,rs;ull val1=0,val2=0;};
struct node{int u,v,Lca,fLca;};
Seg tre[mxn<<5];
I ull get1(node x){return tre[x.u].val1+tre[x.v].val1-tre[x.Lca].val1-tre[x.fLca].val1;}
I ull get2(node x){return tre[x.u].val2+tre[x.v].val2-tre[x.Lca].val2-tre[x.fLca].val2;}
I void push_up(int rot){
tre[rot].val1=tre[tre[rot].ls].val1+tre[tre[rot].rs].val1;
tre[rot].val2=tre[tre[rot].ls].val2+tre[tre[rot].rs].val2;
}
I void build(int& rot,int l,int r){
if(!rot)rot=++cnt;
if(l==r)return;
int mid=(l+r)>>1;
build(tre[rot].ls,l,mid),build(tre[rot].rs,mid+1,r);
}
I void update(int &nrot,int lrot,int l,int r,int k){
nrot=++cnt,tre[nrot]=tre[lrot];
if(l==r)return tre[nrot].val1+=hs1[l],tre[nrot].val2+=hs2[l],void();
int mid=(l+r)>>1;
if(k<=mid)update(tre[nrot].ls,tre[lrot].ls,l,mid,k);
else update(tre[nrot].rs,tre[lrot].rs,mid+1,r,k);
push_up(nrot);
}
I void query(node a,node b,int l,int r){
if(acnt>=k||(get1(a)==get1(b)&&get2(a)==get2(b)))return;
if(l==r)return ans[++acnt]=l,void();
int mid=(l+r)>>1;
query({tre[a.u].ls,tre[a.v].ls,tre[a.Lca].ls,tre[a.fLca].ls},
{tre[b.u].ls,tre[b.v].ls,tre[b.Lca].ls,tre[b.fLca].ls},l,mid),
query({tre[a.u].rs,tre[a.v].rs,tre[a.Lca].rs,tre[a.fLca].rs},
{tre[b.u].rs,tre[b.v].rs,tre[b.Lca].rs,tre[b.fLca].rs},mid+1,r);
}
I void dfs1(int u,int f){
dep[u]=dep[f]+1,sze[u]=1,fa[u]=f;
update(rt[u],rt[f],1,mxn,a[u]);
for(int v:t[u]){
if(v==f)continue;
dfs1(v,u),sze[u]+=sze[v];
if(sze[v]>sze[son[u]])son[u]=v;
}
}
I void dfs2(int u,int Top){
top[u]=Top;
if(son[u])dfs2(son[u],Top);
for(int v:t[u]){
if(v==fa[u]||v==son[u])continue;
dfs2(v,v);
}
}
I int lca(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]])swap(u,v);
u=fa[top[u]];
}
if(dep[u]>dep[v])return v;
else return u;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
hs1[1]=rnd(),hs2[1]=rnd();
for(int i=2;i<=mxn;i++)hs1[i]=rnd(),hs2[i]=rnd();
cin>>n,build(rt[0],1,mxn);
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<n;i++){
int u,v;cin>>u>>v;
t[u].pb(v),t[v].pb(u);
}
dfs1(1,0),dfs2(1,1),cin>>q;
while(q--){
int u1,v1,u2,v2,l1,l2;
cin>>u1>>v1>>u2>>v2>>k;
l1=lca(u1,v1),l2=lca(u2,v2),acnt=0;
query({rt[u1],rt[v1],rt[l1],rt[fa[l1]]},
{rt[u2],rt[v2],rt[l2],rt[fa[l2]]},
1,mxn);
cout<<acnt<<' ';
for(int i=1;i<=acnt;i++)cout<<ans[i]<<' ';
cout<<'\n';
}
return 0;
}
一眼看出二分答案. 我们怎么写 \(\texttt{check}\) 函数呢?
可以根据 \(b\) 数组画出一个内向基环树森林,容易在 \(\rm{O(1)}\) 复杂度内处理从 \(u\) 到 \(v\) 的变换次数.
于是就可以搞一个指针,从左到右、从小到大地去贪心了.
#include<bits/stdc++.h>
using namespace std;
#define mxn 1000010
#define I inline
#define pb push_back
int n,m,a[mxn],b[mxn],ind[mxn];
int cnt,dfn[mxn],tim,cir[mxn],circ[mxn];
int top[mxn],sze[mxn],dep[mxn],cirsze[mxn];
vector<int> t[mxn];
I void cl(){
for(int i=1;i<=m;i++)
t[i].clear(),ind[i]=dfn[i]=0,
cir[i]=circ[i]=top[i]=sze[i]=dep[i]=0;
}
I void topo(){
queue<int> q;
for(int i=1;i<=m;i++)
if(!ind[i])q.push(i);
while(!q.empty()){
int u=q.front();q.pop();
ind[b[u]]--;
if(!ind[b[u]])q.push(b[u]);
}
}
I void dfs2(int u,int Top,int f){
dfn[u]=++tim,top[u]=Top,dep[u]=dep[f]+1,sze[u]=1;
for(int v:t[u])
if(v!=f&&!circ[v])dfs2(v,Top,u),sze[u]+=sze[v];
}
I void dfs1(int u,int Cnt){
ind[u]--,cir[u]=cnt,circ[u]=Cnt,cirsze[cnt]=Cnt;
if(!cir[b[u]])dfs1(b[u],Cnt+1);
dfs2(u,u,u);
}
I int getdis(int u,int v){
if(cir[top[u]]!=cir[top[v]])return 1e9;
if(dfn[v]<=dfn[u]&&dfn[v]+sze[v]-1>=dfn[u])return dep[u]-dep[v];
if(dfn[u]<=dfn[v]&&dfn[u]+sze[u]-1>=dfn[v])return 1e9;
if((!cir[u]&&!cir[v])||(cir[u]&&!cir[v]))return 1e9;
int Sze=cirsze[cir[v]],rot=top[u];
return dep[u]-1+(circ[v]-circ[rot]+Sze)%Sze;
}
I bool check(int x){
int pos=1;
for(int i=1;i<=n;i++){
while(pos<=m&&getdis(a[i],pos)>x)pos++;
if(pos>m)return 0;
}
return 1;
}
I void solve(){
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=m;i++)
cin>>b[i],t[b[i]].pb(i),ind[b[i]]++;
topo();
for(int i=1;i<=m;i++)
if(ind[i])cnt++,dfs1(i,1);
int l=0,r=m+1,ans=m+1;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid))r=mid-1,ans=mid;
else l=mid+1;
}
if(ans>m)cout<<"-1\n";
else cout<<ans<<'\n';
cl();
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int T;cin>>T;while(T--)solve();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】