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 计数即可。转移:
令 \(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}\)。
浙公网安备 33010602011771号