[交互] [ad-hoc] P12541 [APIO2025] Hack!

posted on 2025-05-22 10:49:58 | under | source

介绍一下官解做法。

题意:有 \(n\) 个桶,你想知道 \(n\) 是多少。为此你可以进行若干次询问,每次你给出 \(m\) 个正整数,交互库依次将 \(x_i\) 插入第 \(x_i\bmod n\) 个桶中,每次插入产生原有元素个数的贡献,最后返回总贡献。询问互相独立。\(n\le 10^9\),要求 \(\sum m\le 110000\)

首先观察到若贡献为正,说明必然存在两个元素满足它们的差为 \(n\) 的倍数。那么不难对问题拆解为:先得到询问 \(\{1,x+1\}\) 贡献为正,则 \(n\mid x\);然后依次确定 \(n\) 的每个质因子次幂。

第二部分是容易的,每次去掉一个质因子查询是否仍为倍数即可。步数是 \(2\log n\) 几乎忽略不计。着重考虑第一部分。

我们尝试集合分治,每次划分出一个存在冲突的小集合。

开始前,如何构造覆盖所有元素的集合呢?类似 BSGS,记 \(B\) 为块长,记 \(S_1=\{1\dots B\},S_2=\{B,2B,3B\dots \}\)\(S_1\cup S_2\) 显然覆盖所有数。不过只需考虑 \((5\times 10^8,10^9]\) 范围内的倍数(不在范围就不断翻倍),所以可令 \(S_2=\{5\times 10^8+1+B,5\times 10^8+1+2B\dots \}\)

注意 \(S_1,S_2\) 之间一定有冲突,也就是两端分别在 \(S_1,S_2\)

现在做划分,将 \(S_1\) 分为两部分 \(L,R\)。判定 \(L,S_2\) 之间是否有冲突,有则 \(S_1\gets L\);反之 \(R\)\(S_2\) 之间必然存在冲突,令 \(S_1\gets R\)

考虑判定。首先对 \(L\cup S_2\) 查询,若为零不可能存在冲突。冲突还可能产生在 \(L,S_2\) 内部,不过 \(L\) 内部产生冲突意味着所有桶被 \(L\) 元素塞满,那么肯定存在 \(L,S_2\) 之间的冲突,所以不用管。

不妨假设冲突都在 \(S_2\) 内部,注意到 \(S_2\) 是一个公差为 \(B\) 的等差数列,那么容易列出其内部的冲突总数的式子为 \(G(\frac {lcm(n,B)}{B},|S_2|)\)。其中 \(G(x,y)\) 指依次插入 \(1\dots y\)\(x\) 个桶的总贡献,是可以 \(O(1)\) 计算的。同时 \(G\) 固定 \(y\) 具有单调性,那么二分 \(\frac {lcm(n,B)}B=k\) 即可。得到 \(lcm(n,B)=kB\),查询 \(\{1,kB+1\}\) 是否真的是 \(n\) 的倍数,是那么我们的任务已经完成 \(kB\) 就是所求,否则产生矛盾说明 \(L,S_2\) 之间必然有冲突。

同理可对 \(S_2\) 进行划分。

减少步数肯定 \(S_1\) 砍一刀、\(S_2\) 砍一刀,做一轮就是 \(\frac {|S_1|}2+|S_2|+\frac {|S_1|}{2}+\frac {|S_2|}2=|S_1|+\frac 32 |S_2|\)。我们取 \(B=27000\) 上面的式子比较平衡,分治的总操作数大概是 \(109554‬\)。可以接受。

后记:事实上直接二分答案然后大步小步就是对的,每次只检查一个区间代价是根号区间大小,每次区间大小还会折半,总次数大概 \(108000\),从任何方面完虐官解。

代码

#include "hack.h"
#include<bits/stdc++.h>
using namespace std;

#define LL long long
const int B = 27000;
int n;

long long collisions(std::vector<long long> x);
LL G(int x, int y){
    int h = y / x;
    return 1ll * (h + 1) * h / 2 * (y % x) + 1ll * h * (h - 1) / 2 * (x - y % x);
}
int getF(int S2, LL cnt){
    int L = 1, R = 1e9 + 5, mid;
    while(L + 1 < R){
        mid = L + R >> 1;
        if(G(mid, S2) >= cnt) L = mid;
        else R = mid;
    }
    if(G(L, S2) == cnt) return 1ll * L * B;
    return -1;
}
int hack(){
    vector<LL> S1, S2;
    for(int i = 1; i <= B; ++i) S1.push_back(i);
    int x = 5e8 + 1 + B;
    while(x <= 1e9) {S2.push_back(x); x += B;}     
    S2.push_back(x);
    
    int len = -1;
    vector<LL> SS1, SS2; 
    while(S1.size() > 1 || S2.size() > 1){
        SS1 = S1, SS2 = S2;
        if(S1.size() > 1){
            vector<LL> Lc, L, R;
            for(int i = 0; i < S1.size() / 2; ++i) L.push_back(S1[i]);
            for(int i = S1.size() / 2; i < S1.size(); ++i) R.push_back(S1[i]);
            Lc = L;
            for(auto i : S2) Lc.push_back(i);

            LL Lcnt = collisions(Lc);
            if(Lcnt == 0) S1 = R;
            else{
                int res = getF(S2.size(), Lcnt);
                if(res != -1)
                    if(collisions({1, res + 1}) > 0) {len = res; break;}
                S1 = L;
            }
        }
        if(S2.size() > 1){
            vector<LL> Lc2, L2, R2;
            for(int i = 0; i < S2.size() / 2; ++i) L2.push_back(S2[i]);
            for(int i = S2.size() / 2; i < S2.size(); ++i) R2.push_back(S2[i]);
            Lc2 = L2;
            for(auto i : S1) Lc2.push_back(i);

            LL Lcnt2 = collisions(Lc2);
            if(Lcnt2 == 0) S2 = R2;
            else{ 
                int res2 = getF(L2.size(), Lcnt2);
                if(res2 != -1)
                    if(collisions({1, res2 + 1}) > 0) {len = res2; break;}
                S2 = L2;
            }
        }
    }
    if(len == -1) len = S2[0] - S1[0];
    if(collisions({1, 1 + len}) == 0){
        exit(0);
    }

    vector<int> pc;
    int ljs = len;
    for(int i = 2; i * i <= ljs; ++i)
        if(ljs % i == 0){
            while(ljs % i == 0) ljs /= i;
            pc.push_back(i);
        }
    if(ljs > 1) pc.push_back(ljs);

    for(auto i : pc){
        while(len % i == 0){
            if(collisions({1, len / i + 1}) > 0) len /= i;
            else break;
        }
    }
    return len; 
}
posted @ 2026-01-12 20:15  Zwi  阅读(3)  评论(0)    收藏  举报