1 条题解

  • 0
    @ 2026-4-2 20:38:39

    题目回顾

    我们有两个数组 aabb,长度均为 nn
    必须执行恰好一次操作:选择 lrl \le r,交换 aia_ibib_i 对于所有 lirl \le i \le r

    操作后,定义:

    $$\text{值} = \gcd(a_1, \dots, a_n) + \gcd(b_1, \dots, b_n) $$

    求这个值的最大值,以及达到最大值的不同 (l,r)(l, r) 对的数量。


    第一步:寻找最大可能的 gcd\gcd

    关键观察

    pi=gcd(a1,,ai)p_i = \gcd(a_1, \dots, a_i)qi=gcd(b1,,bi)q_i = \gcd(b_1, \dots, b_i)
    pip_i 要么等于 pi1p_{i-1},要么 pi1/2\le p_{i-1} / 2(因为 gcd\gcd 至少减半)。
    所以 ppqq 中不同的值最多只有 O(logM)O(\log M) 个,其中 M=max(ai,bi)M = \max(a_i, b_i)

    同样,后缀的 gcd\gcd 也满足这个性质。


    固定 AABB 后的分析

    假设我们想让操作后 aagcd\gcdAAbbgcd\gcdBB

    考虑最长的前缀,使得其 gcd\gcdAA(对 aa)或 BB(对 bb)。
    如果交换区间和这个前缀有交集,我们可以不交换交集部分,因为它不会使 gcd\gcd 变大。
    同理对于后缀。

    因此,交换区间应该完全落在“中间部分”,即前缀结束之后、后缀开始之前。
    而中间部分的第一个和最后一个元素必须被交换,否则我们可以延长前缀或后缀。


    充分的前缀/后缀位置

    因此,我们只需要考虑那些“变化点”作为可能的前缀/后缀边界:

    • 对前缀:ii 满足 pipi+1p_i \neq p_{i+1}qiqi+1q_i \neq q_{i+1}
    • 对后缀:类似

    这样的 ii 最多 O(logM)O(\log M) 个。


    暴力枚举中间区间

    对于每个这样的前缀边界 ii(表示前缀 [1,i][1, i] 不交换,交换从 i+1i+1 开始),我们枚举右边界 jji+1i+1nn

    • 维护 ga=gcd(ai+1,,aj)ga = \gcd(a_{i+1}, \dots, a_j)
    • 维护 gb=gcd(bi+1,,bj)gb = \gcd(b_{i+1}, \dots, b_j)

    操作后:

    • aagcd=gcd(前缀 a, 后缀 a, gb)\gcd = \gcd(\text{前缀} \ a,\ \text{后缀} \ a,\ gb)
    • bbgcd=gcd(前缀 b, 后缀 b, ga)\gcd = \gcd(\text{前缀} \ b,\ \text{后缀} \ b,\ ga)

    用前缀 gcd\gcd 数组 pa,pbpa, pb 和后缀 gcd\gcd 数组 sa,sbsa, sb 可以 O(1)O(1) 计算:

    ca=gcd(pa[i],gcd(sa[j+1],gb))ca = \gcd(pa[i], \gcd(sa[j+1], gb)) cb=gcd(pb[i],gcd(sb[j+1],ga))cb = \gcd(pb[i], \gcd(sb[j+1], ga))

    更新最大值。

    因为 jj 只会移动 nn 次,每次 O(1)O(1) 更新 ga,gbga, gb,所以这部分复杂度 O(nlogM)O(n \log M)


    第二步:计算方案数

    我们找到了最大和 S=max(A+B)S = \max(A+B)
    现在要统计有多少 (l,r)(l, r) 能达到 SS


    方法:动态规划

    固定 AABBA+B=SA+B=S),计算有多少区间使得最终 gcd\gcd 分别为 AABB

    定义 dp[i][j]dp[i][j],其中 j=0,1,2j=0,1,2 表示:

    • j=0j=0:还没开始交换
    • j=1j=1:正在交换(区间已开始,未结束)
    • j=2j=2:已结束交换

    初始 dp[1][0]=1dp[1][0] = 1(第一个位置不交换)。

    转移(ii22nn):

    对于当前 ii,检查交换或不交换时,aia_i 是否能被 AA 整除,bib_i 是否能被 BB 整除:

    • 如果不交换(j=0j=0):只能从 k=0k=0
      dp[i][0]=dp[i1][0]dp[i][0] = dp[i-1][0] 如果满足条件

    • 如果开始交换(j=1j=1):可以从 k=0k=0(开始)或 k=1k=1(继续)来
      dp[i][1]=dp[i1][0]+dp[i1][1]dp[i][1] = dp[i-1][0] + dp[i-1][1] 如果满足条件(交换后 ai,bia_i,b_i 满足整除)

    • 如果结束交换(j=2j=2):可以从 k=0k=0(不交换)、k=1k=1(刚好结束)、k=2k=2(已结束)来
      dp[i][2]=dp[i1][0]+dp[i1][1]+dp[i1][2]dp[i][2] = dp[i-1][0] + dp[i-1][1] + dp[i-1][2] 如果满足条件(不交换的情况,即原始 ai,bia_i,b_i 满足整除)

    最终,固定 A,BA,B 的方案数 = dp[n][0]+dp[n][1]+dp[n][2]dp[n][0] + dp[n][1] + dp[n][2]


    特殊情况处理

    • 如果不交换任何区间(即 l=rl=r 不可能,因为必须交换一次)?
      不对,我们允许 l=rl=r 吗?题目说选择 lrl \le r,交换 [l,r][l, r],所以 l=rl=r 是允许的(只交换一个位置)。
      因此“不交换”不是合法操作,必须交换至少一个元素。
      但我们的 DP 包含了 j=0j=0(始终不交换)的情况,这对应 l>rl>r 吗?不,这是无效的,因为必须交换一次。
      所以最后要减去 dp[n][0]dp[n][0] 吗?不,因为 dp[n][0]dp[n][0] 对应的是整个数组都没交换,这是不允许的。
      但标程中,dp[n][0]dp[n][0] 被计入,然后最后特殊处理了“没有交换”的情况。

    实际上,标程的做法:

    • 最后 ans=dp[n][0]+dp[n][1]+dp[n][2]ans = dp[n][0] + dp[n][1] + dp[n][2]
    • 如果 pa[n]+pb[n]==max_gcdpa[n] + pb[n] == max\_gcd(即不交换也能达到最大值),则减去 nn(因为 dp[n][0]dp[n][0] 算了一次,另外 n1n-1 次来自 dp[i][2]dp[i][2]k=0k=0 转移,即交换 [i,i][i, i] 等价于没交换?这需要仔细分析,但按标程处理即可)

    枚举 A,BA,B

    因为 a1a_1b1b_1 不会被交换(l1l \ge 1,但交换 [1,r][1, r] 会交换 a1,b1a_1,b_1,所以 a1,b1a_1,b_1 可以被交换)。
    所以 AA 必须是 gcd(a1,)\gcd(a_1, \dots) 的某个值,即 AAa1a_1 的约数(因为最终 a1a_1 可能被换掉,所以 AA 不一定整除 a1a_1?这里要小心)。
    实际上,最终 AA 必须整除每个 aia_i(在交换后),所以 AA 整除 a1a_1b1b_1(如果 11 在交换区间内)。
    所以枚举 AAa1a_1 的约数和 b1b_1 的约数即可。

    标程只枚举 a1a_1 的约数,并令 B=max_gcdAB = max\_gcd - A,然后检查 BB 是否整除 b1b_1


    时间复杂度

    • 枚举 AAO(a1)O(\sqrt{a_1}) 个约数
    • 每个 AA 调用 solve_onesolve\_oneO(n)O(n) DP
    • 加上前面找最大值的 O(nlogM)O(n \log M)

    总复杂度 O(nlogM+Mn)O(n \log M + \sqrt{M} \cdot n),对 n2105n \le 2\cdot 10^5 可行。


    代码实现(带注释)

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 200005;
    int a[N], b[N];
    int pa[N], pb[N], sa[N], sb[N];
    int n;
    ll dp[N][3];
    
    ll solve_one(ll ga, ll gb) {
        if (ga <= 0 || gb <= 0) return 0;
        if (b[1] % gb) return 0;  // b1 必须被 gb 整除
    
        memset(dp, 0, sizeof(dp));
        dp[1][0] = 1;
    
        for (int i = 2; i <= n; i++) {
            for (int j = 0; j < 3; j++) {
                for (int k = 0; k <= j; k++) {
                    bool ok = true;
    
                    if (j == 1) swap(a[i], b[i]);  // 模拟交换
    
                    if (a[i] % ga) ok = false;
                    if (b[i] % gb) ok = false;
    
                    if (j == 1) swap(a[i], b[i]);  // 换回来
    
                    if (ok) dp[i][j] += dp[i-1][k];
                }
            }
        }
    
        return dp[n][0] + dp[n][1] + dp[n][2];
    }
    
    int main() {
        ios::sync_with_stdio(0);
        cin.tie(0);
    
        int t; cin >> t;
        while (t--) {
            cin >> n;
            for (int i = 1; i <= n; i++) cin >> a[i];
            for (int i = 1; i <= n; i++) cin >> b[i];
    
            // 前缀 gcd
            pa[0] = pb[0] = 0;
            for (int i = 1; i <= n; i++) {
                pa[i] = gcd(pa[i-1], a[i]);
                pb[i] = gcd(pb[i-1], b[i]);
            }
    
            // 后缀 gcd
            sa[n+1] = sb[n+1] = 0;
            for (int i = n; i >= 1; i--) {
                sa[i] = gcd(sa[i+1], a[i]);
                sb[i] = gcd(sb[i+1], b[i]);
            }
    
            // 小数据暴力
            if (n <= 300) {
                int max_gcd = 0, ways = 0;
                for (int l = 1; l <= n; l++) {
                    int ga = 0, gb = 0;
                    for (int r = l; r <= n; r++) {
                        ga = gcd(ga, a[r]);
                        gb = gcd(gb, b[r]);
                        int ca = gcd(pa[l-1], gcd(sa[r+1], gb));
                        int cb = gcd(pb[l-1], gcd(sb[r+1], ga));
                        if (ca + cb > max_gcd) {
                            max_gcd = ca + cb;
                            ways = 1;
                        } else if (ca + cb == max_gcd) {
                            ways++;
                        }
                    }
                }
                cout << max_gcd << " " << ways << "\n";
                continue;
            }
    
            // 大数据:找最大和
            int max_gcd = pa[n] + pb[n];
            for (int i = 2; i <= n; i++) {
                if (pa[i] == pa[i-1] && pb[i] == pb[i-1]) continue;
                int ga = 0, gb = 0;
                for (int j = i; j <= n; j++) {
                    ga = gcd(ga, a[j]);
                    gb = gcd(gb, b[j]);
                    int ca = gcd(pa[i-1], gcd(sa[j+1], gb));
                    int cb = gcd(pb[i-1], gcd(sb[j+1], ga));
                    max_gcd = max(max_gcd, ca + cb);
                }
            }
    
            // 统计方案数
            ll ans = 0;
            for (int d = 1; d * d <= a[1]; d++) {
                if (a[1] % d) continue;
                ans += solve_one(d, max_gcd - d);
                if (d * d != a[1]) {
                    ans += solve_one(a[1] / d, max_gcd - a[1] / d);
                }
            }
    
            // 减去没有交换的情况
            if (pa[n] + pb[n] == max_gcd) ans -= n;
    
            // 加上只交换前缀的情况
            for (int i = 1; i <= n; i++) {
                if (gcd(pa[i], sb[i+1]) + gcd(pb[i], sa[i+1]) == max_gcd) ans++;
            }
    
            cout << max_gcd << " " << ans << "\n";
        }
    
        return 0;
    }
    

    总结

    1. 利用 gcd\gcd 的前缀/后缀快速变化性质,O(logM)O(\log M) 个关键点。
    2. 枚举所有可能的交换区间,O(nlogM)O(n \log M) 找到最大和。
    3. 用 DP 统计方案数,枚举 AAa1a_1 的约数。
    4. 小数据用 O(n2)O(n^2) 暴力,大数据用优化方法。

    这样就能在 O(nlogM+Mn)O(n \log M + \sqrt{M} \cdot n) 内解决问题。

    • 1

    信息

    ID
    6253
    时间
    1000ms
    内存
    256MiB
    难度
    2
    标签
    递交数
    1
    已通过
    1
    上传者