前置知识
例1:小Z的袜子
记
c
n
t
[
i
]
cnt[i]
cnt[i]为颜色
i
i
i的出现次数,那么我们的答案为
∑
c
n
t
[
i
]
(
c
n
t
[
i
]
−
1
)
(
R
−
L
+
1
)
(
R
−
L
)
\frac{\sum cnt[i](cnt[i]-1)}{(R-L+1)(R-L)}
(R−L+1)(R−L)∑cnt[i](cnt[i]−1)
=
(
c
n
t
[
1
]
2
+
c
n
t
[
2
]
2
+
.
.
.
+
c
n
t
[
n
]
2
)
−
(
c
n
t
[
1
]
+
c
n
t
[
2
]
+
.
.
.
+
c
n
t
[
n
]
)
(
R
−
L
+
1
)
(
R
−
L
)
=\frac{(cnt[1]^2+cnt[2]^2+...+cnt[n]^2)-(cnt[1]+cnt[2]+...+cnt[n])}{(R-L+1)(R-L)}
=(R−L+1)(R−L)(cnt[1]2+cnt[2]2+...+cnt[n]2)−(cnt[1]+cnt[2]+...+cnt[n])
=
(
c
n
t
[
1
]
2
+
c
n
t
[
2
]
2
+
.
.
.
+
c
n
t
[
n
]
2
)
−
(
R
−
L
+
1
)
(
R
−
L
+
1
)
(
R
−
L
)
=\frac{(cnt[1]^2+cnt[2]^2+...+cnt[n]^2)-(R-L+1)}{(R-L+1)(R-L)}
=(R−L+1)(R−L)(cnt[1]2+cnt[2]2+...+cnt[n]2)−(R−L+1)
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
struct que{
int l,r,id,pos;
long long a,b;
}ans[50005];
int n,m,f[50005],base,nw=0,cnt[500005],pos[50005];
bool cmp1(que x,que y){
if(pos[x.l]==pos[y.l]) return x.r<y.r;
return x.l<y.l;
}
bool cmp2(que x,que y){
return x.id<y.id;
}
void update(int p,int add)
{
nw-=cnt[f[p]]*cnt[f[p]];
cnt[f[p]]+=add;
nw+=cnt[f[p]]*cnt[f[p]];
}
int gcd(int a,int b){
return (a%b==0)?b:gcd(b,a%b);
}
void solve(){
for(int i=1,l=1,r=0;i<=m;i++){
while(r<ans[i].r) update(++r,1);
while(r>ans[i].r) update(r--,-1);
while(l<ans[i].l) update(l++,-1);
while(l>ans[i].l) update(--l,1);
if(ans[i].l==ans[i].r){
ans[i].a=0;
ans[i].b=1;
continue;
}
ans[i].a=nw-(r-l+1),ans[i].b=(r-l+1)*(r-l);
long long C=gcd(ans[i].a,ans[i].b);
ans[i].a/=C;ans[i].b/=C;
}
}
signed main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++){
scanf("%lld",&f[i]);
}
base=sqrt(n);
for(int i=1;i<=n;i++){
pos[i]=(i-1)/base+1;
}
for(int i=1;i<=m;i++){
scanf("%lld%lld",&ans[i].l,&ans[i].r);
ans[i].id=i;
}
sort(ans+1,ans+m+1,cmp1);
solve();
sort(ans+1,ans+m+1,cmp2);
for(int i=1;i<=m;i++){
cout<<ans[i].a<<"/"<<ans[i].b<<"\n";
}
}
例2:小B的询问
方法同例 1 1 1,代码略。
例3:Mato的文件管理
首先简化题意:给定一个序列,求 [ L , R ] [L,R] [L,R]间的逆序对对数。
有了简化题意,问题就非常好处理了,我们用权值树状数组求逆序对,每次扩区间时将这个数的权值增加 1 1 1,缩区间时减少 1 1 1,顺便加减对应的贡献即可。
Code
#include<bits/stdc++.h>
#define lowbit(x) (x&(-x))
using namespace std;
int Read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return x*f;
}
struct que{
int l,r,id;
}q[50005];
int n,m,a[100005],b[100005],base,belong[100005],L[1005],R[1005],cnt=0;
int c[100005],ans[100005],ct;
bool operator < (que x,que y){
if(belong[x.l]!=belong[y.l]) return belong[x.l]<belong[y.l];
return x.r<y.r;
}
void Add(int x,int p){
while(x<=n){
c[x]+=p;
x+=lowbit(x);
}
}
int query(int x){
int res=0;
while(x>=1){
res+=c[x];
x-=lowbit(x);
}
return res;
}
void solve(){
int l=1,r=0;
for(int i=1;i<=m;i++){
while(q[i].l<l)
l--,Add(a[l],1),ct+=query(a[l]-1);
while(q[i].l>l)
Add(a[l],-1),ct-=query(a[l]-1),l++;
while(q[i].r<r)
Add(a[r],-1),ct-=r-l-query(a[r]),r--;
while(q[i].r>r)
r++,Add(a[r],1),ct+=r-l+1-query(a[r]);
ans[q[i].id]=ct;
}
for(int i=1;i<=m;i++) cout<<ans[i]<<endl;
}
int main(){
n=Read();base=sqrt(n);
for(int i=1;i<=n;i++) a[i]=Read(),b[i]=a[i];
sort(b+1,b+n+1);
int Q=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+Q+1,a[i])-b;
m=Read();
for(int i=1;i<=m;i++){
q[i].l=Read();q[i].r=Read();
q[i].id=i;
}
for(int i=1;i<=n;i+=base){
L[++cnt]=i;R[cnt]=i+base-1;
}
R[cnt]=n;
for(int i=1;i<=cnt;i++){
for(int j=L[i];j<=R[i];j++){
belong[j]=i;
}
}
sort(q+1,q+m+1);
solve();
return 0;
}
例4:Gty的二逼妹子序列
法1 ( O ( n n log n ) ) (O(n\sqrt n\log n)) (O(nnlogn)):
将区间 [ a , b ] [a,b] [a,b]拆为 [ 1 , a − 1 ] [1,a-1] [1,a−1]与 [ 1 , b ] [1,b] [1,b],新加入一个数 x x x时用线段树等数据结构将 [ x , b ] [x,b] [x,b]的值全部 + 1 +1 +1,查询时统计即可。
法2: ( O ( n n ) ) (O(n\sqrt n)) (O(nn)):
用值域分块,写法还是同HH的项链一样,记录每个数是否出现,出现了就 O ( 1 ) O(1) O(1)修改块内的数与该块出现的总种类数,最后在 O ( n ) O(\sqrt n) O(n)统计即可。
Code
#include<bits/stdc++.h>
using namespace std;
int Read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return x*f;
}
int n,m,a[100005],base,L[1005],R[1005],belong[100005],cnt=0,ans[1000005];
int sum[100005],s[100005],ss[100005];
struct que{
int l,r,a,b,id;
}q[1000005];
bool operator < (que x,que y){
if(belong[x.l]!=belong[y.l]) return belong[x.l]<belong[y.l];
return x.r<y.r;
}
void Del(int pos){
s[a[pos]]--;
if(s[a[pos]]==0) sum[belong[a[pos]]]--,ss[a[pos]]=0;
}
void Ins(int pos){
s[a[pos]]++;
if(s[a[pos]]==1) sum[belong[a[pos]]]++,ss[a[pos]]=1;
}
int calc(int l,int r){
int l1=belong[l],r1=belong[r],res=0;
for(int i=l1+1;i<=r1-1;i++){
res+=sum[i];
}
for(int i=l;i<=min(R[l1],r);i++){
res+=ss[i];
}
if(r<=R[l1]) return res;
for(int i=L[r1];i<=r;i++) res+=ss[i];
return res;
}
void solve(){
int l=1,r=0;
for(int i=1;i<=m;i++){
while(q[i].l>l) Del(l++);
while(q[i].l<l) Ins(--l);
while(q[i].r<r) Del(r--);
while(q[i].r>r) Ins(++r);
ans[q[i].id]=calc(q[i].a,q[i].b);
}
for(int i=1;i<=m;i++){
cout<<ans[i]<<endl;
}
}
int main(){
n=Read(),m=Read();
for(int i=1;i<=n;i++) a[i]=Read();
for(int i=1;i<=m;i++){
q[i].l=Read(),q[i].r=Read();
q[i].a=Read(),q[i].b=Read();
q[i].id=i;
}
base=sqrt(n);
for(int i=1;i<=n;i+=base){
L[++cnt]=i,R[cnt]=i+base-1;
}
R[cnt]=n;
for(int i=1;i<=cnt;i++){
for(int j=L[i];j<=R[i];j++){
belong[j]=i;
}
}
sort(q+1,q+m+1);
solve();
return 0;
}
例5:[HNOI2016]序列
假设我们已经得到了区间 [ L , R ] [L,R] [L,R]的答案,那么区间 [ L , R + 1 ] [L,R+1] [L,R+1]的答案就是增加区间 [ L , R + 1 ] , [ L + 1 , R + 1 ] , . . . , [ R , R + 1 ] [L,R+1],[L+1,R+1],...,[R,R+1] [L,R+1],[L+1,R+1],...,[R,R+1]的答案和 a [ R + 1 ] a[R+1] a[R+1]。
那么我们记录每个数前面第一个小于它的数的位置(用单调栈求出) p r e [ i ] pre[i] pre[i],记 f [ i ] f[i] f[i]为右端点为 i i i,左端点在 [ 1 , i ) [1,i) [1,i)间的答案,那么我们有 f [ i ] = f [ p r e [ i ] ] + ( i − p r e [ i ] + 1 ) × a [ i ] f[i]=f[pre[i]]+(i-pre[i]+1)\times a[i] f[i]=f[pre[i]]+(i−pre[i]+1)×a[i]。
求出这些后,我们就可以开始解决我们的问题了。记区间最小值的位置为
p
o
s
pos
pos(用ST表做),那么区间
[
L
,
R
]
[L,R]
[L,R]的答案为
a
n
s
=
a
[
R
]
×
(
R
−
p
r
e
[
R
]
+
1
)
+
a
[
p
r
e
[
R
]
]
×
(
p
r
e
[
R
]
−
p
r
e
[
p
r
e
[
R
]
]
+
1
)
+
.
.
.
+
a
[
p
o
s
]
∗
(
p
o
s
−
L
+
1
)
ans=a[R]\times(R-pre[R]+1)+a[pre[R]]\times(pre[R]-pre[pre[R]]+1)+...+a[pos]*(pos-L+1)
ans=a[R]×(R−pre[R]+1)+a[pre[R]]×(pre[R]−pre[pre[R]]+1)+...+a[pos]∗(pos−L+1)
通过
f
f
f的计算方式,我们可以得知:
f
[
R
]
−
f
[
p
o
s
]
=
a
[
R
]
×
(
R
−
p
r
e
[
R
]
+
1
)
+
a
[
p
r
e
[
R
]
]
×
(
p
r
e
[
R
]
−
p
r
e
[
p
r
e
[
R
]
]
+
1
)
+
.
.
.
f[R]-f[pos]=a[R]\times(R-pre[R]+1)+a[pre[R]]\times(pre[R]-pre[pre[R]]+1)+...
f[R]−f[pos]=a[R]×(R−pre[R]+1)+a[pre[R]]×(pre[R]−pre[pre[R]]+1)+...
由于必有一个数的
p
r
e
=
p
o
s
pre=pos
pre=pos,所以上式总是成立的。
那么由上面两式相减得
a
n
s
=
f
[
R
]
−
f
[
p
o
s
]
+
a
[
p
o
s
]
∗
(
p
o
s
−
L
+
1
)
ans=f[R]-f[pos]+a[pos]*(pos-L+1)
ans=f[R]−f[pos]+a[pos]∗(pos−L+1)
预处理
f
f
f与ST表,即可在
O
(
1
)
O(1)
O(1)时间内完成右边的插入,删除时减去插入的贡献即可。
左边的对称处理即可。
注意:这里的莫队需要先扩再缩,否则会出现玄学错误。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int Read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return x*f;
}
int a[100005],ans[100005],minn[100005][20],n,m,pre[100005],suf[100005];
int base,belong[100005],L[1005],R[1005],cnt=0,s[100005],tp=0;
struct que{
int l,r,id;
}q[100005];
bool operator < (que x,que y){
if(belong[x.l]!=belong[y.l]) return belong[x.l]<belong[y.l];
return x.r<y.r;
}
int my_min(int x,int y){
return (a[x]<a[y])?x:y;
}
void ST(){
for(int j=1;(1<<j)<=n;j++){
for(int i=1;i+(1<<j)-1<=n;i++){
minn[i][j]=my_min(minn[i][j-1],minn[i+(1<<(j-1))][j-1]);
}
}
}
int query(int l,int r){
int k=log(r-l+1)/log(2);
return my_min(minn[l][k],minn[r-(1<<k)+1][k]);
}
namespace s1{
int f[100005];
void prework(){
for(int i=1;i<=n;i++){
f[i]=f[pre[i]]+(i-pre[i])*a[i];
}
}
int calc(int l,int r){
int pos=query(l,r+1);
return (pos-l+1)*a[pos]+f[r+1]-f[pos];
}
}
namespace s2{
int f[100005];
void prework(){
for(int i=n;i>=1;i--){
f[i]=f[suf[i]]+(suf[i]-i)*a[i];
}
}
int calc(int l,int r){
int pos=query(l-1,r);
return (r-pos+1)*a[pos]+f[l-1]-f[pos];
}
}
int res=0;
void solve(){
int l=1,r=0;
for(int i=1;i<=m;i++){
while(r<q[i].r) res+=s1::calc(l,r++);
while(l>q[i].l) res+=s2::calc(l--,r);
while(r>q[i].r) res-=s1::calc(l,--r);
while(l<q[i].l) res-=s2::calc(++l,r);
ans[q[i].id]=res;
}
for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
}
signed main(){
n=Read(),m=Read();
for(int i=1;i<=n;i++){
a[i]=Read();
minn[i][0]=i;
}
ST();
for(int i=1;i<=n;i++){
while(tp&&a[s[tp]]>a[i]) suf[s[tp--]]=i;
pre[i]=s[tp],s[++tp]=i;
}
while(tp) pre[s[tp]]=s[tp-1],suf[s[tp--]]=n+1;
s1::prework();
s2::prework();
base=sqrt(n);
for(int i=1;i<=n;i+=base){
L[++cnt]=i,R[cnt]=i+base-1;
}
R[cnt]=n;
for(int i=1;i<=cnt;i++){
for(int j=L[i];j<=R[i];j++){
belong[j]=i;
}
}
for(int i=1;i<=m;i++){
q[i].l=Read(),q[i].r=Read();
q[i].id=i;
}
sort(q+1,q+m+1);
solve();
return 0;
}