题解:P12541 [APIO2025] Hack!
liuzhangfeiabc · · 题解
题目大意:有一个数
数据范围:
前两档部分分的做法很多,我们直接跳过部分分来看正解。
首先考虑这样一件事:假设我们得到了某一对数
两数冲突意味着两数对
因此我们只需要直接检查所有
现在假设我们已经得到了一个存在冲突的集合
- 将集合
A 划分为两个子集S, T ,分别对每个子集单独检验。如果某个子集内部已经有冲突了,就把问题规模缩小了一半;否则冲突一定发生在S 和T 之间。 - 再将
S, T 分别划分为S_1, S_2, T_1, T_2 ,然后先查询S_1\cup T ,如果有冲突就保留S_1 ,否则保留S_2 ;再查询S_*\cup T_1 (S_* 是上一步中保留的部分)如果有冲突就保留T_1 ,否则保留T_2 。 - 这样通过两次查询就将
S 和T 的规模都缩小了一半,开销为|S|+1.5|T| 。基于等比数列求和可知,整个二分过程的总开销为2|S|+3|T| 。但请注意这没有包含最开始检验A 集合存在冲突,以及检验S 和T 内部是否有冲突的开销;如果S 或T 内部确实存在冲突,得到的总开销将有所减小。
总之,花费
一种容易想到的方案是基于生日悖论:随机选取
但由于要精确分析总开销,需要合理估计使用的集合大小。设
如何做到更优?我们希望免去这样的随机,直接得到一个确定性的存在冲突的集合
容易看出,我们实际上只需要让每个
熟悉数论的小伙伴可能已经想到,数论中的大步小步(BSGS)算法本质上就是在做类似的事。具体而言,构造
然后如何检查
如果
分析一下目前的总开销:首先是二分的主流程,需要确定
最后是得到一对冲突的数之后检查因子的开销:每次检查因子
写一个程序精确计算一下,发现当
当然有一个小优化可以让它变得不是那么紧:考虑二分时交换
注意到上述做法实际上只利用了“集合有没有冲突”而没有利用“具体有多少对冲突”的信息,或许有办法利用这一信息进一步优化。你有更优的做法吗?欢迎讨论~
#include <bits/stdc++.h>
using namespace std;
#define li long long
#define vl vector<li>
#define pb push_back
#define pii pair<int,int>
#define vpi vector<pii>
#define mp make_pair
#define fi first
#define se second
const int N = 18174, M = 27512, Q1 = 118, Q2 = 117;
const int QQ = Q1 * Q2 * 2;
int cnt;
long long collisions(std::vector<long long> x);
bool query(vl x){
cnt += x.size();
return collisions(x) != 0;
}
bool chkQ(){
vl A;A.clear();
for(int i = 1;i <= Q1;++i) A.pb(i * N);
for(int i = 1;i <= Q2;++i) A.pb((Q1 * Q2 + i * Q1 + 1) * N);
return query(A);
}
bool chk2(vl A,vl B){
vl x;x.clear();
for(int i = 0;i < A.size();++i) x.pb(A[i]);
for(int i = 0;i < B.size();++i) x.pb(B[i]);
return query(x);
}
int work(vl A,vl B){
if(A.size() == 1 && B.size() == 1) return abs(A[0] - B[0]);
if(A.size() < B.size()) swap(A,B);
vl A1,A2;A1.clear();A2.clear();
int nn = A.size() >> 1;
for(int i = 0;i < A.size();++i){
if(i < nn) A1.pb(A[i]);
else A2.pb(A[i]);
}
if(chk2(A1,B)) return work(A1,B);
return work(A2,B);
}
bool chk_vpi(vpi x){
int a = 1;
for(int i = 0;i < x.size();++i){
for(int j = 1;j <= x[i].se;++j) a *= x[i].fi;
}
return query({1,a + 1});
}
int final_chk(int x){
int y = x;
vpi pi;pi.clear();
for(int i = 2;i * i <= y;++i) if(y % i == 0){
pi.pb(mp(i,0));
while(y % i == 0){
++pi[pi.size() - 1].se;
y /= i;
}
}
if(y > 1) pi.pb(mp(y,1));
for(int i = 0;i < pi.size();++i){
int l = 0,r = pi[i].se - 1,mid,ans = pi[i].se;
while(l <= r){
mid = (l + r) >> 1;
pi[i].se = mid;
if(chk_vpi(pi)){
ans = mid;
r = mid - 1;
}
else l = mid + 1;
}
pi[i].se = ans;
}
int a = 1;
for(int i = 0;i < pi.size();++i){
for(int j = 1;j <= pi[i].se;++j) a *= pi[i].fi;
}
return a;
}
int hack(){
cnt = 0;
if(chkQ()){
for(int i = 1;i <= QQ;++i){
if(query({1,i * N + 1})) return final_chk(i * N);
}
return -1; // unreachable
}
vl A,B;A.clear();B.clear();
for(int i = 1;i <= N;++i) A.pb(i);
for(int i = 1;i <= M;++i) B.pb(N * M + i * N + 1);
return work(A,B);
}