CF818E Solution
题解
⭐:①可以将整除的条件转化为余数为\(0\)。②枚举左右端点可以找寻单调性,尝试固定一端二分另一端。
暴力的话枚举左右端点,然后\(O(n)\)求乘积余数,总时间复杂度为\(O(n^3)\)。其中区间乘积求余可以使用线段树维护,又可发现,若区间\([i,j]\)的乘积可以被\(k\)整除,则对于\(j<k\le n,[i,k]\)均满足\(k\)整除其乘积。换言之,整除满足单调性。因此可以固定右端点二分左端点,找出满足整除的最小区间。对于每个最小区间\([l,r]\),其所在区间数\(=l\cdot (n-r+1)\),但如果将所有最小区间的该值相加,会有大量重复。因此当我们求出第\(i\)个最小区间\([l_i,r_i]\)时,为避免重复假设\([1,l_{i-1}]\)的元素已被删除,左侧只需考虑\((l_i-l_{i-1})\)个元素。
AC代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
struct node {int l,r,sum;} tr[4*N];
int a[N],k;
void build(int x,int l,int r)
{
tr[x].l=l,tr[x].r=r;
if(l==r) {tr[x].sum=a[l]%k; return;}
int mid=(l+r)/2;
build(x*2,l,mid),build(x*2+1,mid+1,r);
tr[x].sum=tr[x*2].sum*tr[x*2+1].sum%k;
}
int query(int x,int l,int r)
{
if(tr[x].l>=l && tr[x].r<=r) return tr[x].sum;
int ans=1;
if(r>=tr[x*2+1].l) ans=ans*query(x*2+1,l,r)%k;
if(l<=tr[x*2].r) ans=ans*query(x*2,l,r)%k;
return ans;
}
signed main()
{
int n,ans=0,lst=1;//lst:上文所述l[i-1]
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
build(1,1,n);
for(int i=1;i<=n;i++)//i:上文所述r[i]
{
int l=lst,r=i,pos=-1;//pos:上文所述l[i]
while(l<=r)
{
int mid=(l+r)/2;
if(!query(1,mid,i)) pos=mid,l=mid+1;
else r=mid-1;
}
if(pos!=-1) {ans+=(pos-lst+1)*(n-i+1); lst=pos+1;}
}
printf("%lld",ans);
return 0;
}