[动态规划] Codeforces 1497E2 Square-free division (hard version)

题目大意

给定由 \(n(1\leq n\leq 2\cdot 10^5)\) 个正整数组成的数列\(\{a_n\}\),你需要把这个数列分成若干个连续段,使得每个连续段内任意两个不同位置上数字的乘积不为完全平方数。初始时你可以把不多于 \(k (0\leq k\leq 20)\) 个位置上的数修改成任意正整数。请最小化分出的连续段的数目。

题解

容易发现,若两个数的乘积为完全平方数,则两个数分别除尽各自的平方因子后相同。所以我们可以先除尽数列中每个数的平方因子,问题就转化为要使得每个连续段中的数是互不相同的。

\(l[i][x]\) 表示以第 \(i\) 个位置结尾的连续段,之前修改不超过 \(x\) 个位置,该段所能延伸到的最远的左端点的位置。

可以发现,若我们固定住 \(x\),不断递增 \(i\)\(l[i][x]\) 是单调右移的,我们可以维护一个单调右移的指针,并且开一个桶记录该指针到 \(i\) 之间每个数出现的次数,若当前存在两个数出现了两次及以上,就要右移指针,直到每个数在该段内只出现一次。于是我们可以以 \(O(nk)\) 的时间复杂度求得所有 \(l[i][x]\)

\(dp[i][j]\) 表示前 \(i\) 个位置中修改了不超过 \(j\) 个位置,所划分出的最少的连续段数。

那么有 \(dp[i][j]=min(dp[i][j],dp[l[i][x]-1][j-x]+1)\)

时间复杂度 \(O(nk^2)\)

ps: "维护每个位置最远能延伸到的地方"这种思想挺重要的,被这坑了很多次了。

Code

#include <bits/stdc++.h>
using namespace std;
 
#define RG register int
#define LL long long
 
template<typename elemType>
inline void Read(elemType& T) {
    elemType X = 0, w = 0; char ch = 0;
    while (!isdigit(ch)) { w |= ch == '-';ch = getchar(); }
    while (isdigit(ch)) X = (X << 3) + (X << 1) + (ch ^ 48), ch = getchar();
    T = (w ? -X : X);
}
 
int dp[200001][21], l[200001][21], a[200001], b[10000010];
int cnt[10000010];
int T, N, K;
 
int div(int x) {
    int res = 1;
    for (LL i = 2; i * i <= x; ++i) {
        int cnt = 0;
        while (x % i == 0) { x /= i; ++cnt; }
        if (cnt & 1) res *= i;
    }
    if (x != 1) res *= x;
    return res;
}
 
void Solve() {
    for (int k = 0;k <= K;++k) {
        int num = 0, p = 1;
        for (int i = 1;i <= N;++i) {
            ++cnt[a[i]];
            if (cnt[a[i]] > 1) ++num;
            while (num > k) {
                if (cnt[a[p]] > 1) --num;
                --cnt[a[p]];++p;
            }
            l[i][k] = p;
        }
        while (p <= N) { cnt[a[p]] = 0; ++p; }
    }
    for (int i = 1;i <= N;++i)
        for (int k = 0;k <= K;++k)
            for (int x = 0;x <= k;++x)
                dp[i][k] = min(dp[i][k], dp[l[i][x] - 1][k - x] + 1);
    int ans = 1 << 30;
    for (int k = 0;k <= K;++k)
        ans = min(ans, dp[N][k]);
    printf("%d\n", ans);
}
 
int main() {
    Read(T);
    while (T--) {
        Read(N);Read(K);
        for (int i = 1;i <= N;++i) {
            Read(a[i]);
            if (!b[a[i]]) b[a[i]] = div(a[i]);
            a[i] = b[a[i]];
            for (int j = 0;j <= K;++j)
                dp[i][j] = l[i][j] = 0x3f3f3f3f;
        }
        Solve();
    }
    return 0;
}
posted @ 2021-03-26 14:31  AE酱  阅读(99)  评论(0编辑  收藏  举报