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;
}
View Code

 

posted @ 2021-04-22 15:39  UpMing  阅读(165)  评论(0编辑  收藏  举报