Codeforces CF1516D Cut

题目大意

给出一个长度为 n n n 的序列 a a a,以及 q q q 次询问
每次询问给出 l , r l,r l,r,问最少需要把区间 [ l , r ] [l,r] [l,r] 划分成多少段,满足每段内元素的 LCM 等于元素的乘积


这数据范围,这询问方式,一看就是DS题

首先,我们考虑 LCM 的性质。如果一段区间内的数的 LCM 等于所有元素之积,那么这个区间中的数一定两两互质。

我们设 n x t i nxt_i nxti 表示 i i i 后面第一个与 a i a_i ai 互质的数的位置
同时,记 f i f_i fi 为以 i i i 为左端点时,满足区间内数两两互质的最远右端点 + 1 +1 +1,也就是下一个互质区间的左端点

我们考虑从后往前计算 n x t i nxt_i nxti
对于每个质数 p k p_k pk,我们维护 c k c_k ck 表示 p k p_k pk 的倍数出现的最近位置
那么, n x t i nxt_i nxti 就是所有 a i a_i ai 的质因数的 c c c 的最小值

那么显然 f i = min ⁡ k = i n x t i − 1 n x t k f_i=\min\limits_{k=i}^{nxt_i-1}nxt_k fi=k=iminnxti1nxtk,可用线段树解决

最后,我们考虑将 f i f_i fi 进行倍增。 f i , k f_{i,k} fi,k 表示 i i i 后面第 2 k 2^k 2k 个互质区间的左端点。最终在询问时,只需要尽可能地倍增向后跳,并统计答案即可。

时间复杂度 O ( n + n n + n log ⁡ n + n log ⁡ n ) = O ( n n ) \mathcal O(n+ n\sqrt n + n \log n + n \log n)=\mathcal O(n\sqrt n) O(n+nn +nlogn+nlogn)=O(nn )
一个小优化:我们可以用埃氏筛寻找质数,并预处理每个树的质因子。由于 1 0 5 10^5 105 内的数的不同质因子数不超过 6 6 6,所以计算 n x t nxt nxt 的复杂度变为 O ( n ) \mathcal O(n) O(n),总复杂度 O ( n log ⁡ n ) \mathcal O(n \log n) O(nlogn)

#include
#include
#include
#include
#include
using namespace std;
const int Maxn=1e5+10;
const int Maxm=Maxn<<2;
const int inf=0x3f3f3f3f;
vector <int> c[Maxn];
int b[Maxn],a[Maxn];
int f[Maxn][30];
int p[Maxn],id[Maxn];
bool vis[Maxn];
int minv[Maxm];
int n,m,q,cnt;
int gcd(int x,int y)
{
	if(!y)return x;
	return gcd(y,x%y);
}
inline void push_up(int k)
{
	minv[k]=min(minv[k<<1],minv[k<<1|1]);
}
void build(int k,int l,int r)
{
	if(l==r)
	{
		minv[k]=f[l][0];
		return;
	}
	int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	push_up(k);
}
int query(int k,int l,int r,int x,int y)
{
	if(x<=l && r<=y)return minv[k];
	int mid=(l+r)>>1,ret=inf;
	if(x<=mid)ret=query(k<<1,l,mid,x,y);
	if(mid<y)ret=min(ret,query(k<<1|1,mid+1,r,x,y));
	return ret;
}
inline int read()
{
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0' && ch<='9')s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
	return s*w;
}
int main()
{
	// freopen("in.txt","r",stdin);
	n=read(),q=read();
	for(int i=1;i<=n;++i)
	a[i]=read(),m=max(m,a[i]);
	vis[1]=1;
	for(int i=2;i<=m;++i)
	{
		if(vis[i])continue;
		b[++cnt]=i,id[i]=cnt;
		c[i].push_back(cnt);
		for(int j=(i<<1);j<=m;j+=i)
		vis[j]=1,c[j].push_back(cnt);
	}
	for(int i=1;i<=cnt;++i)
	p[i]=n+1;
	fill(f[n+1],f[n+1]+25,n+1);
	for(int i=n;i;--i)
	{
		f[i][0]=n+1;
		for(int j=0;j<c[a[i]].size();++j)
		{
			f[i][0]=min(f[i][0],p[c[a[i]][j]]);
			p[c[a[i]][j]]=i;
		}
	}
	build(1,1,n);
	for(int i=n;i;--i)
	{
		f[i][0]=query(1,1,n,i,f[i][0]-1);
		for(int j=1;j<=20;++j)
		f[i][j]=f[f[i][j-1]][j-1];
	}
	while(q--)
	{
		int l=read(),r=read();
		int x=l,ret=0;
		for(int i=20;i>=0;--i)
		if(f[x][i]<=r)
		ret+=(1<<i),x=f[x][i];
		++ret;
		printf("%d\n",ret);
	}
	return 0;
}

你可能感兴趣的:(题解,#Codeforces,数论,倍增,线段树,数据结构)