一道题
这是学弟问我的一道题, 竟然是以前cf的题, 我大概想对了一多半吧, 如果和我队友一起的话应该是能够A掉的。。 首先两个数相乘是平方数, 那么把这个数质因数分解的话, 每个因数的幂一定是2的倍数, 假设一个因子是\(a^{2i+1}\), 那我们不妨看成\(a^1\), 当然对答案是没有影响的, 那么问题就变成, 分组后每组不能有相同的数字,那么我们就想到dp的做法。 设dp[i][j]表示前i个数, 改变j次最小的分组数。当然我们需要知道, 每个数前面改变i次所能达到的左端点, 我们记为l[i][j], 这个可以用双指针\(O(nk)\)求出。转移方程的话就很简单。
\(dp[i][j] = min(dp[i][j], dp[l[i][q] - 1][j - q] + 1) (q≤j)\)
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 1e5 + 10;
const int M = 1e7 + 10;
const int maxn = 3e3;
template < typename T > inline void read(T &x) {
x = 0; T ff = 1, ch = getchar();
while (!isdigit(ch)) {
if (ch == '-') ff = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
x *= ff;
}
vector < int > v;
int n, k, a[N], dp[N][21], l[N][21];
int cnt[M], vis[M];
inline void pre() {
for (int i = 2; i <= maxn; ++i) {
if (!vis[i]) {
vis[i] = i;
v.push_back(i);
}
for (int j = 0; j < v.size(); ++j) {
if (v[j] > vis[i] || v[j] > maxn / i) break;
vis[i * v[j]] = v[j];
}
}
}
inline void work() {
for (int i = 1; i <= n; ++i) {
int cnt = 1;
for (int j = 0; j < v.size(); ++j) {
int y = v[j], w = 0;
while (a[i] % y == 0) a[i] /= y, ++w;
if (w & 1) cnt *= y;
if (a[i] == 1) break;
}
a[i] *= cnt;
}
}
int main() {
// freopen("team.in", "r", stdin);
// freopen("team.out", "w", stdout);
read(n), read(k);
for (int i = 1; i <= n; ++i) read(a[i]);
pre();
work();
for (int i = 0; i <= k; ++i) {
int L = 1, sum = 0;
for (int j = 1; j <= n; ++j) {
++cnt[a[j]];
if (cnt[a[j]] > 1) ++sum;
while (sum > i) {
--cnt[a[L]];
if (cnt[a[L]]) --sum;
++L;
}
l[j][i] = L;
}
while (L <= n) --cnt[a[L]], ++L;
}
memset(dp, 0x3f, sizeof(dp));
for (int i = 0; i <= k; ++i) dp[0][i] = 0;
for (int i = 1; i <= n; ++i)
for (int j = 0; j <= k; ++j)
for (int q = 0; q <= j; ++q)
dp[i][j] = min(dp[i][j], dp[l[i][q] - 1][j - q] + 1);
int ans = INF;
for (int i = 0; i <= k; ++i) ans = min(ans, dp[n][i]);
printf("%d\n", ans);
return 0;
}