题意
Codeforces 1497E2 Square-free division (hard version)
题解
根据算数基本定理,设 a = ∑ p i e i a=\sum p_i^{e_i} a=∑piei。若两个数字的乘积为完全平方数,则两个数字指数项 e i e_i ei 为奇数的质因子 p i p_i pi 构成的集合相同,那么可以用这样的质因子的乘积 ∏ p i \prod p_i ∏pi 唯一的表示。线性筛 O ( N ) O(N) O(N) 预处理出 [ 1 , A ] [1,A] [1,A] 的最小质因数,则可以 O ( N log A ) O(N\log A) O(NlogA) 求解每个元素的唯一表示。
则同一段中的限制条件转化为不存在两个数字的唯一表示相同。枚举最后一段的左端点,
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 表示从元素
[
1
,
i
]
[1,i]
[1,i] 中改变不超过
j
j
j 个数字可以划分的最小段数,总时间复杂度
O
(
N
2
K
2
)
O(N^2K^2)
O(N2K2),显然难以胜任。观察到
j
j
j 相同时,
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 随着
i
i
i 递增而单调不减,那么若能求解出以
i
i
i 为右端点改变不超过
j
j
j 个数字的满足条件区间的最左侧的端点
l
b
[
i
]
[
j
]
lb[i][j]
lb[i][j],则
D
P
DP
DP 总时间复杂度减少为
O
(
N
K
2
)
O(NK^2)
O(NK2)。
d
p
[
i
]
[
j
]
=
min
0
≤
k
≤
j
d
p
[
l
b
[
i
]
[
k
]
−
1
]
[
j
−
k
]
+
1
dp[i][j]=\min\limits_{0\leq k\leq j}dp[lb[i][k]-1][j-k]+1
dp[i][j]=0≤k≤jmindp[lb[i][k]−1][j−k]+1 观察到
j
j
j 相同时,随着满足条件的区间右界的左移,左界不会向右移动,则可以使用尺取法
O
(
K
N
)
O(KN)
O(KN) 求解
l
b
[
i
]
[
j
]
lb[i][j]
lb[i][j]。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 200005, maxk = 22, maxa = 10000005, inf = 0x3f3f3f3f;
int T, N, K, A[maxn], id[maxn];
int np, prime[maxa], minp[maxa];
int cnt[maxa], lb[maxn][maxk], dp[maxn][maxk];
void sieve()
{
for (int i = 2; i < maxa; ++i)
{
if (!minp[i])
minp[i] = i, prime[++np] = i;
for (int j = 1; j <= np && i * prime[j] < maxa; ++j)
{
minp[i * prime[j]] = prime[j];
if (minp[i] == prime[j])
break;
}
}
}
int main()
{
sieve();
scanf("%d", &T);
while (T--)
{
scanf("%d%d", &N, &K);
for (int i = 1; i <= N; ++i)
scanf("%d", A + i);
for (int i = 1; i <= N; ++i)
{
int a = A[i], x = 1, cnt = 0, lst = 0;
while (a != 1)
{
int p = minp[a];
if (p == lst)
++cnt;
else
{
if (cnt & 1)
x *= lst;
cnt = 1, lst = p;
}
a /= p;
}
if (cnt & 1)
x *= lst;
id[i] = x;
}
for (int j = 0; j <= K; ++j)
{
int l = N + 1, sum = 0;
for (int i = N; i; --i)
{
while (l - 1 >= 1 && sum + (cnt[id[l - 1]] > 0) <= j)
--l, sum += cnt[id[l]] > 0, ++cnt[id[l]];
lb[i][j] = l;
sum -= --cnt[id[i]] > 0;
}
}
for (int i = 1; i <= N; ++i)
for (int j = 0; j <= K; ++j)
dp[i][j] = inf;
for (int j = 0; j <= K; ++j)
dp[0][j] = 0;
for (int i = 1; i <= N; ++i)
for (int j = 0; j <= K; ++j)
for (int k = 0; k <= j; ++k)
dp[i][j] = min(dp[i][j], dp[lb[i][k] - 1][j - k] + 1);
printf("%d\n", dp[N][K]);
}
return 0;
}