CF2211E 歪解

CF2211E Solution

其实这个强制在线让我感到意义不明。

正常思路部分

这个路径必是"垂直的",因此适合树形dp

F1

定义状态 \(dp_{u,i}\) 表示节点 \(u\),以 \(u\) 为端点的链的端点上 \(\gcd\{a\}=i\) 时的最小值。

这个 \(i\) 看似很大,有 \(d(a)\)(因数个数)个,一点也不小。

F2

考虑一条合法的链,因为其 \(\gcd\{a\}\ne 1\),所以 \(\gcd\{a\}\) 必有一个质因子(废话),因此我们可以用这个质因子代替 \(\gcd\{a\}\) 计数。

所以我们将第二位状态改为:\(dp_{u,i}\) 表示节点 \(u\),以 \(u\) 为端点的链的端点上 \(i\mid\gcd\{a\}\) 时的最小值。这样第二维是 \(\omega(a)\) 的,转移就 \(n\omega(a)\) 的就行了。

哈哈,这样做就忽略了一个重要的事情:分解质因数最快是 \(\sqrt[4]{a}\) 的,\(n\sqrt[4]{a}\) 会炸。

歪解部分

分解不完,那就不分解完就行了吗!我们只用 \(T\) 以内的质数对每个 \(a\) 进行分解质因数,称 \(a_i\) 这样分解后剩余的数为 \(r_i\) 。对于小于 \(T\) 的质数我们采用 F2 计数;而对于 \(r_i\) ,其最多有 \(\log_{T}\frac{a}{T}\) 个质因子,这样 \(r_i\) 最多有 \(2^{\log_{T}\frac{a}{T}}\) 个因数,采用 F1 计数即可。转移:

\[dp1_{x,i}+\sum_{v\ne x}\min(\min_j\{dp1_{v,j}\},\min_j\{{dp2_{v,j}}\})\to dp1_{u,i} \]

\[dp2_{x,i}+\sum_{v\ne x}\min(\min_j\{dp1_{v,j}\},\min_j\{{dp2_{v,j}}\})\to dp2_{u,j},\gcd(i,j)\ne1 \]

\[1+\sum_{v}\min(\min_j\{dp1_{v,j}\},\min_j\{{dp2_{v,j}}\})\to dp1_{u,i} \]

\[1+\sum_{v}\min(\min_j\{dp1_{v,j}\},\min_j\{{dp2_{v,j}}\})\to dp2_{u,i} \]

\(B=\log_{T}\frac{a}{T}\),时间复杂度为 \(O(n\frac{T}{\ln T}+nB\log n+n2^B\log A)\),取 \(T=3573\),即用前 \(500\) 个素数分解时能过,此时 \(B=5\)(想这个balance并不难)。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int P = 3580;
const int N = 2e5 + 5;
const int OME = 16;
const int F = 35;
const int INF = 1e9;
int pri[P], pcnt, n, dp1[N][OME], plis[N][OME], cntp[N], mi[N], dp2[N][F], cntg[N];
LL a[N], res[N], glis[N][F], tmp[N * F];
vector <int> G[N];
inline void Solve() {
    scanf("%d", &n);
    for (int i = 1;i <= n;i++) {
        cntp[i] = cntg[i] = 0;
        G[i].clear();
        for (int j = 0;j < OME;j++)
            dp1[i][j] = INF;
        for (int j = 0;j < F;j++)
            dp2[i][j] = INF;
    }
    for (int u = n;u >= 1;u--) {
        scanf("%lld", &a[u]);
        res[u] = a[u];
        for (int j = 1;j <= pcnt;j++)
            if (res[u] % pri[j] == 0) {
                plis[u][++cntp[u]] = pri[j];
                while (res[u] % pri[j] == 0)
                    res[u] /= pri[j];
            }
        int k;
        scanf("%d", &k);
        int sum = 0;
        while (k--) {
            int v;
            scanf("%d", &v);
            mi[v] = INF;
            for (int i = 1;i <= cntp[v];i++)
                mi[v] = min(mi[v], dp1[v][i]);
            for (int i = 1;i <= cntg[v];i++) {
                tmp[++cntg[u]] = __gcd(res[u], glis[v][i]);
                mi[v] = min(mi[v], dp2[v][i]);
            }
            sum += mi[v];
            G[u].push_back(v);
        }
        tmp[++cntg[u]] = res[u];
        sort(tmp + 1, tmp + 1 + cntg[u]);
        cntg[u] = unique(tmp + 1, tmp + 1 + cntg[u]) - tmp - 1;
        for (int i = 1;i <= cntg[u];i++)
            glis[u][i] = tmp[i];
        for (int v : G[u]) {
            //使用小质数转移
            for (int i = 1, j = 1;i <= cntp[u];i++) {
                while (j <= cntp[v] && plis[u][i] > plis[v][j])
                    j++;
                if (plis[u][i] != plis[v][j])
                    continue;
                dp1[u][i] = min(dp1[u][i], dp1[v][j] + sum - mi[v]);
            }
            //使用500p以外因数转移
            for (int i = 1;i <= cntg[v];i++) {
                LL x = __gcd(glis[v][i], res[u]);
                if (x == 1)
                    continue;
                int p = lower_bound(glis[u] + 1, glis[u] + cntg[u] + 1, x) - glis[u];
                dp2[u][p] = min(dp2[u][p], dp2[v][i] + sum - mi[v]);
            }
        }
        //小质数不转移。
        for (int i = 1;i <= cntp[u];i++)
            dp1[u][i] = min(dp1[u][i], sum + 1);
        //500p以外因数不转移
        dp2[u][cntg[u]] = min(dp2[u][cntg[u]], sum + 1);
        //这里转移和上文中写的不一样,实则是等价的
        int ans = INF;
        for (int i = 1;i <= cntp[u];i++)
            ans = min(ans, dp1[u][i]);
        for (int i = 1;i <= cntg[u];i++)
            ans = min(ans, dp2[u][i]);
        printf("%d\n", ans);
        fflush(stdout);
    }
}
int main() {
    for (int i = 2;i <= P - 5;i++) {
        bool fail = false;
        for (int j = 2;1ll * j * j <= i;j++)
            if (i % j == 0) {
                fail = true;
                break;
            }
        if (!fail)
            pri[++pcnt] = i;
    }
    int t;
    scanf("%d", &t);
    while (t--)
        Solve();
}

\(2.1\text{s}\),也还好吧,榜一写的官解都要 \(1.6\text{s}\)

posted @ 2026-03-29 13:09  yangbaich  阅读(13)  评论(0)    收藏  举报