题目
t(t<=10)组样例,每次给定一个长度为n的数组(n<=5e5),第i个数为ai(1<=ai<=1e6),
求有多少区间[l,r]满足,区间[l,r]内最小值可以整除区间[l,r]内最大值
思路来源
官方题解
题解
感觉是计数题常用的套路,不过好像没用到组合数学
先用单调栈预处理出lbe、rb、ls、rs四个数组
lbe(left big or equal):i往左第一个>=a[i]的位置,不存在的话为0
rb(right big):i往右第一个>a[i]的位置,不存在的话为n+1
ls(left small):i往左第一个<a[i]的位置,不存在的话为0
rs(right small):i往右第一个>a[i]的位置,不存在的话为n+1
预处理出1e6内每个数的因子,
以下考虑枚举每一个位置i作最大值,
枚举a[i]的因子d,把d当最小值,
其出现的位置的vector为pos[d],是事先预处理的,
在pos[d]内根据i的位置二分出恰在i左侧的位置j,和恰在i右侧的位置k,
然后分四种情况讨论:
1.仅有<i的j,没有>=i的k,需要满足(ls[j],rs[j])覆盖i,且(lbe[i],rb[i])覆盖j,
此时,左端点可以在(max(lbe[i],ls[j]),j]内取,右端点在[i,min(rb[i],rs[j]))内取,贡献为二者乘积
2.有=i的,显然满足[ls[i],rs[i]]覆盖i,且[lbe[i],rb[i]]覆盖i,
此时,左端点可以在(max(lbe[i],ls[i]),i]内取,右端点在[i,min(rb[i],rs[i]))内取,贡献为二者乘积
此时i左侧和右侧的最小值不可能再覆盖i,否则区间能融合成一个区间
lbe和rs的作用类似,相当于钦定相同的值钦定右边的"大",
即右边的可以扩展到左边,左边的不能扩展到右边
3.仅有>i的k,没有<i的j,需要满足(ls[k],rs[k])覆盖i,且(lbe[i],rb[i])覆盖k,
此时,左端点可以在(max(lbe[i],ls[k]),i]内取,右端点在[k,min(rb[i],rs[k]))内取,贡献为二者乘积
4.同时有<i的j,和>i的k
其中,j的贡献,需要满足(ls[j],rs[j])覆盖i,且(lbe[i],rb[i])覆盖j,
此时,左端点可以在(max(lbe[i],ls[j]),j]内取,右端点在[i,min(rb[i],rs[j]))内取,贡献为二者乘积
而k的贡献,需要满足(ls[k],rs[k])覆盖i,且(lbe[i],rb[i])覆盖k,
此时,左端点可以在(max(j,lbe[i],ls[k]),i]内取,右端点在[k,min(rb[i],rs[k]))内取,贡献为二者乘积
这里需要令左端点>j,是为了避免计数重复,
假设一个区间最小值为d,且其中出现了两个或两个以上d的位置,
我们把这个区间段贡献,只在它这个区间中第一次出现的d上统计
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+10,M=1e6+10;
int t,n,a[N];
int stk[N],c,lbe[N],rb[N],ls[N],rs[N];
vector<int>fac[M],pos[M];
int main(){
for(int i=1;i<M;++i){
for(int j=i;j<M;j+=i){
fac[j].push_back(i);
}
}
scanf("%d",&t);
while(t--){
scanf("%d",&n);
a[0]=M;
stk[c=1]=0;
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
pos[a[i]].push_back(i);
while(c && a[stk[c]]<a[i]){
c--;
}
lbe[i]=stk[c];
stk[++c]=i;
}
a[n+1]=M;
stk[c=1]=n+1;
for(int i=n;i>=1;--i){
while(c && a[stk[c]]<=a[i]){
c--;
}
rb[i]=stk[c];
stk[++c]=i;
}
a[0]=0;
stk[c=1]=0;
for(int i=1;i<=n;++i){
while(c && a[stk[c]]>=a[i]){
c--;
}
ls[i]=stk[c];
stk[++c]=i;
}
a[n+1]=0;
stk[c=1]=n+1;
for(int i=n;i>=1;--i){
while(c && a[stk[c]]>=a[i]){
c--;
}
rs[i]=stk[c];
stk[++c]=i;
}
ll ans=0;
for(int i=1;i<=n;++i){
for(auto &d:fac[a[i]]){
auto &b=pos[d];
if(!b.size())continue;
int y=lower_bound(b.begin(),b.end(),i)-b.begin();
if(y==b.end()-b.begin()){
if(lbe[i]<b[y-1] && rs[b[y-1]]>i)ans+=1ll*(b[y-1]-max(lbe[i],ls[b[y-1]]))*(min(rb[i],rs[b[y-1]])-i);
continue;
}
if(b[y]==i){
ans+=1ll*(i-max(lbe[i],ls[i]))*(min(rb[i],rs[i])-i);
continue;
}
if(y==0){
if(ls[b[y]]<i && rb[i]>b[y])ans+=1ll*(i-max(lbe[i],ls[b[y]]))*(min(rb[i],rs[b[y]])-b[y]);
continue;
}
if(lbe[i]<b[y-1] && rs[b[y-1]]>i)ans+=1ll*(b[y-1]-max(lbe[i],ls[b[y-1]]))*(min(rb[i],rs[b[y-1]])-i);
if(ls[b[y]]<i && rb[i]>b[y])ans+=1ll*(i-max(b[y-1],max(lbe[i],ls[b[y]])))*(min(rb[i],rs[b[y]])-b[y]);
}
}
printf("%lld\n",ans);
for(int i=1;i<=n;++i){
pos[a[i]].clear();
}
}
return 0;
}
TODO
补一下官方题解/jiangly代码的写法