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;
}
原文地址:https://www.cnblogs.com/violetholmes/p/14530011.html