NOI模拟 刀客球
涉及知识点:数论,单调栈
题意
给你一串长度为 的序列,值域 ,求该序列所有区间的 之积,对 取模。
思路
形如求 “所有区间” 的某种值的这种题目,肯定不能直接直接思考每个区间的贡献,因为但就枚举区间的基本复杂度 是很难降下来的,一般这种情况有两种解决方式:
- 顺序遍历所有点,对于节点 可以在 或者 的复杂度之内求出以 结尾的所有区间的贡献。
- 遍历所有节点,并且可以快速计算出节点 决定了哪些节点的贡献。本题采用的是这种做法。
首先考虑如何计算一个区间的 ,直接利用 是不现实的,不同于 会越操作越小,这样计算 会导致非常大的结果。但还有另外一种计算方式,即我们将每个数质因数分解,然后将每个质因数的次方数取区间最大值,最后相乘即可得到区间 ,形式化地说,设 ,那么区间 的区间 即为 。
于是我们想到可以分开计算每个质因数的贡献,对于第 个数 的第 个质因数 ,记 分解后 上的指数为 , 做出的贡献即为 往左往右拓展直到某个数质因数分解后 的指数大于 ,形式化的讲,找到一个极大的区间 使得 ,如果有 ,满足 (两边都取小于等于可能会导致重复计数,所以在实现时要左闭右开)。
实现
为了常数更优,我们将分解出的质因数分成两部分计算,以 为分界线,大于此分界线的质因数在每个数中最多出现 次,上文所述的”能拓展的区间“很显然就是左边第一个与右边第一个含有相同质因子的数,但是由于统计时”左开右闭“(代码里是左开右闭方便写,左闭右开也行),实际上计数时右端点是序列尾端点;而小于等于分界线的数通过单调栈实现 找左右能拓展的最远点。
代码
#include<bits/stdc++.h>
using namespace std;
#ifdef ONLINE_JUDGE
#define getchar __getchar
inline char __getchar(){
static char ch[1<<20],*l,*r;
return (l==r&&(r=(l=ch)+fread(ch,1,1<<20,stdin),l==r))?EOF:*l++;
}
#endif
template<class T>inline void rd(T &x){
T res=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9'){if(ch=='-')f=-1; ch=getchar();}
while('0'<=ch && ch<='9'){res=res*10+ch-'0';ch=getchar();}
x=res*f;
}
template<class T>inline void wt(T x,char endch='\0'){
static char wtbuff[20];
static int wtptr;
if(x==0){
putchar('0');
}
else{
if(x<0){x=-x;putchar('-');}
wtptr=0;
while(x){wtbuff[wtptr++]=x%10+'0';x/=10;}
while(wtptr--) putchar(wtbuff[wtptr]);
}
if(endch!='\0') putchar(endch);
}
typedef long long LL;
const LL P=1e9+7;
const int MAXN=100005,MAXA=1e7;
int n,pre[MAXN],aft[MAXN],cnt[MAXN];
LL a[MAXN],ans=1;
inline LL qpow(LL x,LL y){
x%=P;
LL res=1;
while(y){
if(y&1) res=res*x%P;
x=x*x%P;
y>>=1;
}
return res;
}
int pri[MAXA],pri_cnt=0;
bool vis[MAXA+5];
inline void get_prime(){
for(int i=2;i<=MAXA;i++){
if(!vis[i]) pri[++pri_cnt]=i;
for(int j=1;j<=pri_cnt;j++){
if(1LL*pri[j]*i>MAXA) break;
vis[pri[j]*i]=1;
if(i%pri[j]==0) break;
}
}
}
int stk[MAXN],stp;
pair<int,int>tot[MAXN];
int totcnt=0;
int main(){
get_prime();
rd(n);
for(int i=1;i<=n;i++){
rd(a[i]);
}
for(int j=1;1LL*pri[j]*pri[j]<=MAXA;j++){
int it=pri[j];
for(int i=1;i<=n;i++){
cnt[i]=0;
while(a[i]%it==0){
a[i]/=it;
cnt[i]++;
}
}
stp=1;
stk[1]=0;
cnt[0]=0x3f3f3f3f;
for(int i=1;i<=n;i++){
while((stp) && (cnt[i]>cnt[stk[stp]])) stp--;
pre[i]=stk[stp];stk[++stp]=i;
}
stp=1;
stk[1]=n+1;
cnt[n+1]=0x3f3f3f3f;
for(int i=n;i>=1;i--){
while((stp) && (cnt[i]>=cnt[stk[stp]])) stp--;
aft[i]=stk[stp];stk[++stp]=i;
}
for(int i=1;i<=n;i++){
ans=ans*qpow(it,1LL*cnt[i]*(i-pre[i])*(aft[i]-i))%P;
}
}
for(int i=1;i<=n;i++){
if(a[i]>1) tot[++totcnt]=make_pair(a[i],i);
}
sort(tot+1,tot+1+totcnt);
for(int l=1,r,lst;l<=totcnt;){
r=l;
while(r+1<=totcnt && tot[l].first==tot[r+1].first) r++;
lst=0;
for(int i=l;i<=r;i++){
ans=ans*qpow(tot[i].first,1LL*(tot[i].second-lst)*(n-tot[i].second+1))%P;
lst=tot[i].second;
}
l=r+1;
}
wt(ans,'\n');
return 0;
}
处理小于分界线的数的循环:
枚举质因数 ,分解质因数 ,单调栈 ,综合最大复杂度 , 为值域
处理大于分界线的数的循环:
双指针,
本文作者:MessageBoxA
本文链接:https://www.cnblogs.com/SkyNet-PKN/p/18183375
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
2023-05-10 USACO23FEB-Gold/洛谷P9127 Equal Sum Subarrays