Codeforces Round #708 (Div. 2)E1. Square-free division

Square-free division

原题传送门
题目大意:

给你一个数列,要你将其划分成若干个连续的区间,使得在每个区间中,任意两个数的乘积不是完全平方数,求满足这个条件的所有划分方法中,区间个数的最小值。

知识点分析:
1.欧拉筛的应用
2.算数基本定理
3.stl的应用(map)

那么我们一步一步的在分析求解这道题目。

1.首先我们需要分析任意两个数的乘积不为完全平方数的条件,只有知道这个,我们才能获得划分区间的依据,才能将题目继续做下去。

考虑使用算术基本定理先将数分解成质数形式,我们从数列中任取两个数分别为 a i , a j a_i,a_j ai,aj

然后将这两个数都分解成质数的形式。‘
a i = p b 1 a 1 ∗ p b 2 a 2 . . . p b k a k \displaystyle a_i=p_{b_1}^{a_1}*p_{b_2}^{a_2}...p_{b_k}^{a_k} ai=pb1a1pb2a2...pbkak
a j = p c 1 d 1 ∗ p c 2 d 2 . . . p c m d m \displaystyle a_j=p_{c_1}^{d_1}*p_{c_2}^{d_2}...p_{c_m}^{d_m} aj=pc1d1pc2d2...pcmdm

然后我们接着分析,一个数是完全平方数,那么它的质数分解后的形式中,每个质数的指数一定是偶数,否则,它将不是完全平方数,因为它不能为整体贡献一个平方出来,即二次方。
那么对于 a i , a j \displaystyle a_i,a_j ai,aj,我们只要记录它们质因子,然后将它们两个的质因子乘起来,判断每个质因子的指数是否存在奇数即可,但是如果直接这样做,貌似有点复杂,复杂度也会上升,我们需要更进一步的分析每个质数及其指数的贡献。我们知道,偶数次幂的质数,都可以写成完全平方的形式,也就是说,它对整体的贡献可以忽略,思考一下,如果我们要判断一个数是否可以写成完全平方数,只需要看它分解后的指数中是否存在奇数,而奇数这个性质,不论它到底有多大,它是奇数,那么在奇偶性质上等价于1,然后不论是做加减法,还是乘法,它的性质变化都与1相同。考虑到这个后,数列中的每一项对一个乘积的贡献都等价于它的质因子中,指数是奇数的质因子的乘积。偶数次都可以忽略对吧。

2.对于这样两个化简后的表达式,这里记为mask[i],mask[j]。如果它们的乘积是完全平方式,那么它们的mask一定相等。
即: m a s k [ i ] = = m a s k [ j ] mask[i]==mask[j] mask[i]==mask[j]
否则,一定存在一个质因子,它的指数不为偶数,那么这两个数就不能构成完全平方数。

3.然后我们就可以贪心的去做这件事情,我们要让这个区间个数尽可能的小,我们就从第一个数开始去取区间,尽可能的维护当前的区间,让其最长即可。

4.但是这里有个小细节要注意,我们在判断当前区间最长值得时候,是要找到当前轮中重复出现得那个数,一旦出现之后,就要刷新这个信息,但这样会大大增加我们程序得运行效率,但是没有关系,我们可以用L来表示当前轮,每次遇到重复得就更新这个标记,同时不断更新map得值来表示某个数最近出现是在哪一轮。

5.但是由于数据量较大,我们不能够直接对所有数进行质因数分解,而官方给出得方法真的是太巧妙了。下面我们着重分析这个变形得欧拉筛。
首先我们要分解每个数,我们需要预处理出在1e7范围内的每个质数,这样在分解的时候才更方便,不用重复去找,更何况这还是多组数据,但是我们在想一想,是不是还可以进一步优化呢。我们知道,我们使用欧拉筛是因为它更高效,它保证了每个数只被筛一次(即用每个数的最小质因子去筛),也就是说,我们在筛的过程中就能获取每个数的最小质因子,我们只需要保存这个信息,这样,我们就可以避开枚举,直接找到每个数的最小质因子,然后除去,得到一个新的数,重复上述过程即可,复杂度大大降低。

具体看代码,有注释。

//#include<bits/stdc++.h> ----万能头----
#include <iostream>
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<cstring>
#include<cmath>
#include<queue>
#include<list>
#include<map>
#include<unordered_map>
using namespace std;
const int p = 1e9+7;
const int N = 1e7+5;
const int maxn = 1e5+10;
const long long INF = 1<<31;
#define REP(i,n) for(int i = 1; i <= n; ++i)
#define REPA(i,j,n) for(int i = j; i <= n; ++i)
#define RREP(i,n) for(int i = n; i >= 1; --i)
#define REPR(i,j,n) for(int i = n; i >= j; --i)
typedef long long ll;
typedef pair<ll,ll> PII;
ll n,m,t;
ll k;
ll a[maxn];
ll mask[maxn];
ll mind[N];
vector<int> q;//存放质数
void get_pr() //欧拉筛变形,记录最小质因数
{
    for(int i = 2;i <= 1e7; ++i)
    {
        if(mind[i] == 0)//这里跟欧拉筛一样,如果当前位没有被筛过,则为质数
        {
            mind[i] = i;//质数的最小质因子为其本身
            q.emplace_back(i);//这样貌似效率更高相对于push
        }
        for(auto &pr : q)//枚举之前获得的质数
        {
            if(pr*i > 1e7 || pr > mind[i])break;//这里跟欧拉筛稍稍有些不一样,但本质上完全一致,k*i防止越界
             // 本来在欧拉筛中是判断当前数中如果包含了当前枚举的质数后,就要停止,防止重复筛,而这里由于我们记录的是每个数的最小质因数。
                // 所以我们只要判断当前的质数是否大于当前数的最小质因数
            mind[pr*i] = pr;
        }
    }

}

void solve() {
    cin>>n>>k;
    for(int i = 1;i <= n; ++i)scanf("%lld",&a[i]);
    for(int i = 1;i <= n; ++i){
        ll cnt = 0, x = a[i], last = 0;
        mask[i] = 1;//init
        while(x > 1){ //如果现在剩下的数仍然大于1,则意味着,当前数还可以继续被质数分解
            ll p = mind[x];//获取最小质因子
            if(p == last){//如果当前数的最小质因子与刚除掉的质因子相同,即意味着,最小质因子的指数不为1,则记录指数。
                //由于最小质因子一定是随着x的减小而单调递增的,由算术基本定理可以很好理解
                ++cnt;
            }
            else{//这里就意味着如果当前的最小质因子与上一次除去的不同,我们就要计算上一个质因子对mask的贡献
                if(cnt&1)//如果出现了奇数次,则要计算贡献
                    mask[i]*=last;
                last = p;//更新指数
                cnt = 1;
            }
            x/=p;
        }
        if(cnt & 1)mask[i]*=last;//不要忘记如果最后一个质因子出现了奇数次,他的贡献可不能遗忘
    }
    ll ans = 1,L = 0;
    map<ll,ll> mp;
    for(int i = 1;i <= n; ++i){
        if(mp.find(mask[i]) != mp.end() && mp[mask[i]] >= L){
            ++ans;
            L = i;//用来更新当前在哪个区间
        }
        mp[mask[i]] = i;//不断更新当前出现的mask的值所对应的区间
    }
    cout<<ans<<'\n';
}





int main() {
    //ios::sync_with_stdio(0);
    //cin.tie(0);
    //cout.tie(0);

    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);

    get_pr();
    scanf("%lld",&t);
    while(t--)
        solve();

    fclose(stdin);
    fclose(stdout);

}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值