Codeforces 1516D - Cut - (倍增优化+尺取+质因子分解)
题目大意:
给你一个序列和m次询问,每次询问区间[L,R]求这个区间的最小划分,使得每个划分的子串乘积和=LCM
题目思路:
step1:
先从最简单的思路开始,首先要保证一个区间的乘积和等于LCM,那么这个区间的任意两个数都是互质的
考虑暴力的扩展区间,从[L,R]扩展到[L,R+1]的时候,扫一遍现有的区间,看看是否gcd=1,带上询问时间复杂度O(n^3)
step2:
其实这个暴力扩展区间的时候,我们没有必要扫一遍现有的区间,我每次扩展一个数的时候,把她的质因子用一个桶存起来,然后对于
这个将要拓展的数字,我们把它质因子分解,看他的所有的质因子是不是出现过,出现过就清空桶,划分新的段,时间复杂度O(logn*n^2)或者O(sqrt(n)*n^2)
step3:
时间复杂度还是不够好,我们还需要优化掉一个n
题目要求是划分尽可能少的区间,也就是对于一个数字,我们需要找他控制到的最远的地方(没有数和他不互质),相信大家都懂这个控制的意思
其实到step2清空桶那个地方就应该想到尺取的,因为我们没有必要把整个桶都清空,我们需要一直清空到没有某种质因子就可以了
这样话我们采取logn的质因子分解,时间复杂度就是O((n+n)*logn), 这仅仅是预处理!!!,不带每次询问
复杂度退化:
n=100000,m=100000,每次询问都是【1,100000】,序列全部等于2
这样即使我们预处理出来了,每个数控制到的最远的地方,我们的复杂度还没有降下来
倍增优化:
f[j][i]代表从j点跳2^i次到的点的位置
每一次从L跳到R+1, 那么[L,R]一定是一个合法的区间
f[L][0] = R,初始化成我们在尺取中维护出来的东西(最远控制到的地方+1)
每次跳的次数就是这个区间划分成的区间个数
那上面那种复杂度退化的情况就可以解决了,最后答案是跳的次数加一(理解为切一刀产生两个块)
具体细节见代码
CODE:
int p[maxn],x,vis[maxn],mi[maxn]; void oula() { for(int i=2 ;i<=maxn-2 ;i++) { if(vis[i]==0) p[++x] = i,mi[i] = i; for(int j=1 ;j<=x&&i*p[j]<=maxn-2 ;j++) { vis[i*p[j]]=1; mi[i*p[j]] = p[j]; if(i%p[j]==0) break; } } } int n,qq,a[maxn],f[maxn][22]; int kind[maxn]; void del(int pos) { int num = a[pos]; while(num>1) { int t = mi[num]; kind[t]--; num/=t; } if(num>1) { int t = num; kind[t]--; num/=t; } } int main() { oula(); n=read(),qq=read(); rep(i,1,n) a[i] = read(); int R=1,L=1; while(R<=n) { int num = a[R]; int num2 = num; while(num>1) { int t = mi[num]; while(kind[t]) f[L][0]=R, del(L),L++; num/=t; } if(num>1) { int t = num; while(kind[t]) f[L][0]=R, del(L),L++; num/=t; } while(num2>1) { int t = mi[num2]; kind[t]++; num2/=t; } if(num2>1) { kind[num2]++; } R++; } while(L<=n) f[L][0] =R,L++; //rep(i,1,n) cout<<f[i][0]<<endl; //f[j][i]代表从j点跳2^i次到的点的位置 //每一次从L跳到R+1, 那么[L,R]一定是一个合法的区间 rep(i,0,20) f[n+1][i] = n+1;//防止越界 for(int i=1 ;i<=20 ;i++) { for(int j=1 ;j<=n ;j++) { f[j][i] = f[f[j][i-1]][i-1]; } } while(qq--) { int L = read(); int R = read(); int ans =0; for(int i=20 ;i>=0;i--) { if(f[L][i]<=R) { ans+=(1<<i); L = f[L][i]; } } out(ans+1); puts(""); } return 0; }