E2. Square-free division (hard version) DP
题目大意:
给你一个序列 (a) ,你需要把这个序列分成几个区间,使得任意一个区间不存在两个数的乘积是一个平方数,你可以进行 (k) 次将一个值改成另一个值,问最少可以分成几个区间?
题解:
很容易想到这个是一个 (DP) ,然后 (DP) 的定义是: (dp[i][j]) 表示到 (i) 位置,更改了 (j) 次的最少的区间。
然后转移也可以很快的想到 (dp[i][j] = min(dp[i][j],dp[x][k]+1))
但是显然这个是一个 (O(n*n)) 的转移,然后发现可以预处理一个数组 (left[i][j]) 表示从 (i) 这个位置,往左修改了 (j) 个值的最长的区间长度。
(left[i][j]) 首先枚举 (j) 然后用双指针扫过来即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 4e3 + 10;
const int maxm = 2e5 + 10;
int isp[maxn],cnt,v[maxn];
void init() {
cnt = 0;
for (int i = 2; i < maxn; i++) {
if (!v[i]) {
isp[++cnt] = i;
v[i] = i;
}
for (int j = 1; j <= cnt; j++) {
if (isp[j] * i >= maxn) break;
v[isp[j] * i] = isp[j];
if (i % isp[j] == 0) break;
}
}
}
int a[maxm];
map<int,int>mp;
int lc[maxm][22],pre[maxm],vis[maxm],dp[maxm][22];
void deal(int x) {
int ans = 1, now = a[x];
for (int i = 1; i <= cnt; i++) {
int cur = 0;
while (now % isp[i] == 0) cur++, now /= isp[i];
if (cur & 1) ans = ans * isp[i];
if (now == 1) break;
}
if (now != 1) ans = ans * now;
a[x] = ans;
}
/*
* left[i][j] 表示从i往左修改j的最长的长度
* pre[i] 表示i这个位置的上一个值在哪
*/
int main() {
int T;
init();
scanf("%d", &T);
while (T--) {
int n, k;
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
deal(i);
}
mp.clear();
for (int i = 1; i <= n; i++) {
pre[i] = mp[a[i]];
mp[a[i]] = i;
memset(lc[i], 0, sizeof(lc[i]));
memset(dp[i], 0x3f, sizeof(dp[i]));
}
for (int j = 0; j <= k; j++) {
for (int i = 1; i <= n; i++) vis[i] = 0;
int l = 1, r = 1, ans = 0;
while (r <= n) {
if (pre[r] >= l) vis[pre[r]]++, ans++;
while (ans > j) ans-=vis[l],l++;
lc[r][j] = r - l + 1;
// printf("lc[%d][%d]=%d l = %d ans = %d
",r,j,lc[r][j],l,ans);
r++;
}
}
int ans = 1e9;
memset(dp[0],0,sizeof(dp[0]));
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= k; j++) {
for (int x = 0; x + j <= k; x++) {
dp[i][j + x] = min(dp[i][j + x], dp[i - lc[i][x]][j] + 1);
if (i == n) ans = min(ans, dp[i][j + x]);
}
}
}
printf("%d
", ans);
}
return 0;
}
/*
1
7 1
13 13 1 13 3 1 3
*/