zoukankan      html  css  js  c++  java
  • [动态规划] Codeforces 1497E2 Square-free division (hard version)

    题目大意

    给定由 (n(1leq nleq 2cdot 10^5)) 个正整数组成的数列({a_n}),你需要把这个数列分成若干个连续段,使得每个连续段内任意两个不同位置上数字的乘积不为完全平方数。初始时你可以把不多于 (k (0leq kleq 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
    ", 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;
    }
    
  • 相关阅读:
    get与post的区别
    shell脚本之变量替换
    Oracle sql性能优化
    HTTP协议报头
    Oracle查看表空间和删除表空间
    shell脚本之cat和wc命令
    java设计模式之单例模式
    Wireshark基本介绍和学习TCP三次握手转
    wrong number of arguments (1 for 2)
    PHP生成.url文件 网站常用的保存到桌面功能
  • 原文地址:https://www.cnblogs.com/AEMShana/p/14582191.html
Copyright © 2011-2022 走看看