题记
大二到大三小学期(其实是暑假了,但是因为这一系列周赛开始于小学期,就用小学期称呼它了)第三次排位赛I题。这次周赛爆零了,整个过程非常的困难,并不是自暴自弃式爆零,而是因为是俱乐部内部排位赛,总共五场排位赛,前两场都不好,想着这一场一定要跟上,这个执念对我影响很大,一度让我心态非常焦虑和着急,看着榜上过题,坐在身边的同学有代码可敲,我在思路上挂机,就越发着急,结果就是越着急越没有思路吧,不能沉下心来好好想某一道题。
这道题是我补题的第一顺位,因为陈朝潮学长打完跟我说这是个签到题。
签到个鬼,1800到2500的题集区间,这个玩意儿2100分。虽然补了,但是想想当时排位赛场的话我是没有可能做出来的。比如这个基础筛法就只看过方法从来没有在题目上写过。
但是既然补掉了,下次就心里有数了。
题意
对于一个数组,希望把其中的元素分成尽量少的若干个组,使得每个组中,任意两个元素之积为完全平方数。设这个分成的组的数量为k,现在给出一个长度为n的数组a,求出对于 1 <= k <= n的每一个k值,对应k为答案的a数组的连续子序列的数量。
思路
在思考这个问题的时候,最核心的一点是,我们对给定数组元素进行如上分组时,采取什么策略才可以使得组数最少?不妨尝试依次考虑每一个元素,不断将未分组的元素添加到已分组的集合中。现在来看当前待分组的元素,我们首先会想到是不是能够把它添加到之前的某一个组中去?如果能,我们来考虑这个可以添加进的组和这个元素有什么共性。假设组中有元素x,待分组的元素为y,那么x * y是一个完全平方数。完全平方数意味着,分解质因数之后,这个数的每一个因子出现的频率都为偶数。那么我们把x,y分别分解质因数,去除各自因数中成对的因子,剩下的因子仍可以和另一个数中的因子凑成一对。比如:
若x为40,则x = 2 * 2 * 2 * 5,取出成对因子后,x = 2 * 5。现在要有一个y和x相乘后仍为完全平方数,那么显然y中必然需要有2,5两个因子。除此之外,y中只能再出现成对的质因子。比如y = 2 * 5 * 7 * 7就可以和x构成完全平方数,而y = 2 * 5 * 5就不可以。
这样事情就很清楚了,去除成对的质因子后,剩下的因子完全相同,即剩下的数完全相等的两个数才能分为一组。当然,0是一个例外,可以任意分为一组。
于是思路就有了:将所有数取出完全平方因子,一个区间中有多少不同的数,这个区间就需要分成几组。而计算答案的时候,只需要枚举区间的左端点,推进区间右端点来枚举每个区间,记录每个数前一个和它相同数的位置,就可以统计答案。这一点可以见参考博客。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int max_n = 5e3 + 50;
int n;
vector<int> pri; //保存素数
int the_before[max_n];
int ans[max_n];
int a[max_n];
int del_per(int x) {
if (x == 0)
return x;
//用每一个素数去试探着去掉完全平方
for (int i = 0; i < (int)pri.size(); i++) {
int divi = pri[i] * pri[i];
while(x % divi == 0)
x /= divi;
}
return x;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
//对a[i]进行去完全平方数化
{
//筛出素数
bool notp[10001];
memset(notp, false, sizeof(notp));
for (int i = 2; i <= 10000; i++) {
if(notp[i])
continue;
pri.push_back(i);
for (int j = i << 1; j <= 10000; j += i) {
notp[j] = true;
}
}
// cout << "素数筛完成" << endl;
//对每一个a[i]去掉完全平方
// cout << "去平方之后:" << endl;
for (int i = 1; i <= n; i++) {
a[i] = del_per(a[i]);
// printf("%d: %d\n", i, a[i]);
}
}
//记录对于每一个a[i]离它最近的前一个和它值相同的位置
map<int, int> pos;
for (int i = 1; i <= n; i++) {
the_before[i] = pos[a[i]];
// printf("the_before[%d]:%d\n", i, the_before[i]);
//如果之前没有的话,这个值就是0,就会比左区间小,含义上来说可以完成答案的统计工作
pos[a[i]] = i;
}
// cout << "记录对于每一个a[i]离它最近的前一个和它值相同的位置" << endl;
//扫描每一个连续区间,对该区间最小可以进行的分组cnt,统计到答案中
for (int i = 1; i <= n; i++) {
int cnt = 0;
for (int j = i; j <= n; j++) {
if(a[j] != 0 && the_before[j] < i) //写错成过the_before[a[j]],数组的含义要搞清楚啊
cnt++;
// if(cnt == 0)
// cnt++; //这种写法是错误的,如果首位是0,第二位是1,答案应该是1,这么写会变成2
ans[max(1, cnt)]++;
}
}
for (int i = 1; i <= n; i++) {
printf("%d", ans[i]);
if(i != n) printf(" ");
else printf("\n");
}
return 0;
}