cf1497 E2. Square-free division (hard version)

题意:

给定正整数数组,可以把其中的不超过 k 个数分别改成任意正整数。问数组最少能切成几段,使得每段中都没有两个数的积是平方数

\(n\le 1e5, 1\le a_i\le 1e7, 0\le k\le 20\)

思路:

先把每个数处理成它的次数为奇的质因子之积,那么两个数的积是平方数当且仅当处理后的两个数相等

然后预处理数组 \(left[i][j]\) 表示最小左端点 \(L\),满足 \(a_L,a_{L+1},\cdots ,a_i\) 换掉不超过 \(k\) 个数后任两个数之积都不是平方数

怎么预处理呢?注意到对于固定的 \(j\),随着 \(i\) 减小 \(left[i][j]\) 不增。开个桶用于计数,双指针处理即可

最后 dp:\(f[i][j]\) 表示处理到位置 \(i\),换掉不超过 \(j\) 个数的最小段数。枚举最后一段换了多少个,则 \(f(i,j)=\min_{x=0}^j \{f(i-left[i][x]-1,j-x)+1 \}\)

const signed N = 2e5 + 10, M = 1e7 + 10;

int minp[M]; void init(int n = 1e7) { //预处理每个数的最小质因子
    for(int i = 2; i <= n; i++) if(!minp[i])
        for(int j = i; j <= n; j += i) minp[j] = i;
}

int n, k, a[N];
int l[N][25], cnt[M];

void sol() {
    cin >> n >> k;
    for(int i = 1; i <= n; i++) {
        cin >> a[i];
        
        int tmp = 1; //a[i]的次数为奇的质因子之积
        while(a[i] > 1) {
            int p = minp[a[i]], c = 0;
            while(a[i] % p == 0) a[i] /= p, c ^= 1;
            if(c) tmp *= p;
        }
        a[i] = tmp;
    }
    
    for(int j = 0; j <= k; j++) { //预处理l[i][j]
        int L = n + 1, same = 0;
        for(int i = n; i; i--) {
            while(L > 1 && same + !!cnt[a[L-1]] <= j)
                --L, same += !!cnt[a[L]], cnt[a[L]]++;
            l[i][j] = L;
            if(cnt[a[i]] > 1) same--;
            cnt[a[i]]--;
        }
    }
    
    vector<vector<int>> f(n+1, vector<int>(k+1, 1e9));
    fill(f[0].begin(), f[0].end(), 0); //初始化
    for(int i = 1; i <= n; i++)
        for(int j = 0; j <= k; j++)
            for(int jj = 0; jj <= j; jj++)
                f[i][j] = min(f[i][j], f[l[i][jj]-1][j-jj] + 1);
    
    cout << *min_element(f[n].begin(), f[n].end()) << '\n';
}
posted @ 2022-09-16 14:55  Bellala  阅读(34)  评论(0)    收藏  举报