cf1497 E2. Square-free division (hard version)
题意:
给定正整数数组,可以把其中的不超过 k 个数分别改成任意正整数。问数组最少能切成几段,使得每段中都没有两个数的积是平方数
\(n\le 1e5, 1\le a_i\le 1e7, 0\le k\le 20\)
思路:
先把每个数处理成它的次数为奇的质因子之积,那么两个数的积是平方数当且仅当处理后的两个数相等
然后预处理数组 \(left[i][j]\) 表示最小左端点 \(L\),满足 \(a_L,a_{L+1},\cdots ,a_i\) 换掉不超过 \(k\) 个数后任两个数之积都不是平方数
怎么预处理呢?注意到对于固定的 \(j\),随着 \(i\) 减小 \(left[i][j]\) 不增。开个桶用于计数,双指针处理即可
最后 dp:\(f[i][j]\) 表示处理到位置 \(i\),换掉不超过 \(j\) 个数的最小段数。枚举最后一段换了多少个,则 \(f(i,j)=\min_{x=0}^j \{f(i-left[i][x]-1,j-x)+1 \}\)
const signed N = 2e5 + 10, M = 1e7 + 10;
int minp[M]; void init(int n = 1e7) { //预处理每个数的最小质因子
for(int i = 2; i <= n; i++) if(!minp[i])
for(int j = i; j <= n; j += i) minp[j] = i;
}
int n, k, a[N];
int l[N][25], cnt[M];
void sol() {
cin >> n >> k;
for(int i = 1; i <= n; i++) {
cin >> a[i];
int tmp = 1; //a[i]的次数为奇的质因子之积
while(a[i] > 1) {
int p = minp[a[i]], c = 0;
while(a[i] % p == 0) a[i] /= p, c ^= 1;
if(c) tmp *= p;
}
a[i] = tmp;
}
for(int j = 0; j <= k; j++) { //预处理l[i][j]
int L = n + 1, same = 0;
for(int i = n; i; i--) {
while(L > 1 && same + !!cnt[a[L-1]] <= j)
--L, same += !!cnt[a[L]], cnt[a[L]]++;
l[i][j] = L;
if(cnt[a[i]] > 1) same--;
cnt[a[i]]--;
}
}
vector<vector<int>> f(n+1, vector<int>(k+1, 1e9));
fill(f[0].begin(), f[0].end(), 0); //初始化
for(int i = 1; i <= n; i++)
for(int j = 0; j <= k; j++)
for(int jj = 0; jj <= j; jj++)
f[i][j] = min(f[i][j], f[l[i][jj]-1][j-jj] + 1);
cout << *min_element(f[n].begin(), f[n].end()) << '\n';
}

浙公网安备 33010602011771号