[题解]T723953 「LDOI R2」平方数 Square

题目:# P17765 「LDOI R2」平方数 / Square

题目描述

有 $n$ 个给定的二元组 $(a_i, b_i)$。

称一个长度为 $m\ (m>1)$ 的序列 $p_1, p_2, \dots, p_m\ (1\le p_i\le n)$ 是好的序列,当且仅当它满足以下条件:

  • $p_i$ 互不相同。

  • $a_{p_1}$ 是完全平方数。

  • $b_{p_m}$ 是完全平方数。

  • 对于所有的 $1 \le i < m$,$a_{p_{i+1}} b_{p_i}$ 是完全平方数。

求所有好的序列中 $m$ 的最小值,若不存在好的序列输出 $-1$。

输入格式

本题有多组测试用例。

第一行输入一个正整数 $T$ 表示测试用例组数,接下来对于每一组测试用例:

第一行输入一个正整数 $n$。

接下来 $n$ 行,每行输入两个正整数,第 $i+1$ 行的两个数表示 $a_i, b_i$。

输出格式

对于每组测试用例:

若存在好的序列,则输出所有好的序列中,$m$ 的最小值,若不存在好的序列输出 $-1$。

输入输出样例 #1

输入 #1

3
5
4 5
20 9
8 9
16 3
12 2
1
1 1
2
1 1
1 1

输出 #1

2
-1
2

输入输出样例 #2

输入 #2

1
9
5 9
8 2
9 3
5 4
6 5
3 5
2 4
8 5
9 3

输出 #2

3

说明/提示

样例解释

样例 $1$ 解释:对于第一组测试用例,考虑序列 $[1,2]$,$a_1=4$、$b_1a_2 = 100$、$b_2=9$ 都是完全平方数,该序列长度为 $2$。$[4,5,3]$ 也是一个好序列,但是它的长度为 $3$,不是最优的。

数据范围

本题采用 Subtask 捆绑测试。

Subtask $\sum n \le$ 分数
$1$ $10$ $20$
$2$ $400$ $40$
$3$ $2000$ $40$

对于所有数据,$1\le a_i, b_i \le 10^5$,$1 \le n, \sum n \le 2000$。

提示

$n$ 是完全平方数,当且仅当存在整数 $x$ 满足 $n=x^2$。

第 0 步:先看清要判什么

题目约束相邻两项:

$$b_{p_i}\cdot a_{p_{i+1}}\ \text{是完全平方数}\qquad(1\le i<m)$$

同时要求首项 a 是平方,末项 b 是平方。

由数对$(a_{p_i},b_{p_i})$很容易想到一条有向边$u_{a_i}$->$u_{b_i}$

所以将题目转化为"寻找一条最短路,要求入点、出点、边权均为平方数"


第 1 步:计算平方核 K(x)

直接计算边权的积非常难算,所以通过平方性质(即"平方由偶数个相同系数组成")引入平方核概念优化整张图:

定义:平方核$K(x)$表示只保留$K(x)$出现奇数次的质因子,每个取一次。

$$K(x)=\prod_{e_p(x)\ \text{为奇}} p$$

即:$K(x)=x \div (\text{能整除 } x \text{ 的最大平方数})$,"把所有能开方的部分开掉"。

$x$ 分解 $K(x)$ 说明
8 $2^3$ 2 指数 3 是奇,只取一个 2
12 $2^2\cdot3$ 3 4 被开掉
18 $2\cdot3^2$ 2 9 被开掉
4, 9, 16 平方数 1 全是偶数次

第 2 步:建图

根据两点的平方核乘积判断连通并建图:

  • 点:每个输入位置(下标 $i$)(注意不是数值)。
  • 边:$i\to j$ 当且仅当 $i\ne j$ 且 $K(b_i)=K(a_j)$(承接上一点)。
if (i != j && kb[i] == ka[j]) graph[i].push_back(j);

注:

  • 整张图有向且为反向边:边表示"$j$ 接在 $i$ 后面",用的是 $i$ 的尾巴 b 和 $j$ 的头 a(写成 ka[i]==kb[j] )是反向边,原因:两二元组是否可以相连依据是$b_p·a_{p+1}$当且仅当剩余数(平方核)k[b[i]]==k[a[j]]才能相连,$a_p/a_{p+1}$与$b_p/b_{p+1}$本身就是连通的,不需要考虑建图连通性
  • 必须 $i\ne j$:防止自环

第 3 步:多源 BFS

  • 起点集合:$K(a_i)=1$ 的点(可以当序列第一个)。
  • 终点集合:$K(b_i)=1$ 的点(可以当序列最后一个)。
  • 所有起点一起入队,dist 初始化为 1,让距离直接等于序列长度。
  • 出队时检查:dist[u] > 1 (防止自环)且 $K(b_u)=1$,第一个命中的就是最优解。
while (!q.empty()) {
    int u = q.front(); 
    q.pop();
    if (dist[u] > 1 && kb[u] == 1) { 
        ans = dist[u]; 
        break; 
    }  // 命中终点
    for (int v : graph[u])
        if (dist[v] == -1) { 
            dist[v] = dist[u] + 1; 
            q.push(v); 
        }
}

复杂度

步骤 时间 空间
算核 $O(n\sqrt{A})$ $O(n)$
建图 $O(n^2)$ $O(n+E)$,$E\le n^2$
BFS $O(n+E)$ $O(n)$

$n\le2000$,最坏约 $4\times10^6$ 条边,实测约 16 MB、几十毫秒,完全够用。


思维链:

相邻乘积是平方
↓ 只关心指数奇偶
平方核 K(x)
↓ $K(b_i) == K(a_j)$
有向边 i → j
↓ 多源 + 距离=点数
最短序列长度

源代码:

#include<bits/stdc++.h>
using namespace std;
using ll=long long;

int lpf(int x){	//求平方核
    int ans=1;
    for(int i=2;i*i<=x;i++){
        int m=0;
        while(x%i==0){
            x/=i;
            m^=1;
        }
        if(m) ans*=i;	//指数为奇,将该因子计入
    }
    if(x>1) ans*=x;		//剩余质数一并乘入结果
    return ans;
}


void solve(){
    int n;
    cin>>n;
    int both=0;
    vector<int> ks(n),ke(n);
    for(int i=0;i<n;i++){
        int st,ed;
        cin>>st>>ed;
        st=lpf(st);
        ed=lpf(ed);
        if(st==1&&ed==st){	//特殊形态:a,b均为平方数,只要有两个就可以直接返回2
            both++;
        }
        ks[i]=st;
        ke[i]=ed;
    }
    if(both>=2){
        cout<<2<<'\n';
        return;
    }
    vector<vector<int>> grid(n);	//邻接表
    vector<int> dist(n,-1);		//记录最短距离/判断访问
    queue<int> q;
    for(int i=0;i<n;i++){
        if(ks[i]==1){	//先判为平方数的a对应的点,直接入队
            dist[i]=1;
            q.push(i);
        }
        for(int j=0;j<n;j++){
            if(i!=j&&ke[i]==ks[j]){	//非自环/首尾相乘为平方数->连通
                grid[i].push_back(j);
            }
        }
    }

    int ans=-1;
    while(!q.empty()){
        int x=q.front();
        q.pop();
        if(ke[x]==1&&dist[x]>1){	//结尾点b为平方数,直接得答案
            ans=dist[x];
            break;
        }
        for(auto& nxt:grid[x]){
            if(dist[nxt]==-1){		//继续遍历未访问点
                dist[nxt]=dist[x]+1;
                q.push(nxt);
            }
        }
    }
    cout<<ans<<'\n';
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t=1;
    cin>>t;
    while(t--){
        solve();
    }
    return 0;
}
posted @ 2026-11-11 15:07  yiliyu  阅读(4)  评论(0)    收藏  举报