【题解】 CF1493F Enchanted Matrix 交互+border+复杂度分析
Legend
Link \(\textrm{to Codeforces}\)。
Editorial
这个询问次数限制非常鬼畜:\(3 \cdot \lfloor \log_2 (n+m)\rfloor\),而且答案的 \((r,c)\) 一定满足 \(r|n,c|m\) 看起来就跟质因子的指数和和有什么奇怪的关系。
先来分析一下题目。我们画一个小矩形,然后把它复制若干份平铺,看看能不能发现什么性质。
Observation 1: 行列是独立的,我们只要求出行和列的方案然后乘起来就行了。
显然,但证明我还没想到什么好办法。
于是 2D 问题变成了序列问题,也就是现在有一个长 \(n\) 的序列,你每次可以询问两个不相交子串,求它的最小周期。
最小周期?考虑怎么判断一个长度 \(k\) 是不是 \(n\) 的周期,根据定义来说,必须这样:
- 将 \(k\) 个元素捆在一起,则 \(\frac{n}{k}\) 个元素完全一致。
正好交互器支持询问元素是否一致,看来这就是正解的方向!但是两个两个问太慢了,怎么办呢?
你如果对于字符串理论非常熟悉,那么你可能会想到 border 可以用来判定周期,只需要询问一个前缀和后缀是否相等就行了。而对于 border 不相交的情况,我们直接询问就行。但是 border 是有可能相交的,如下图。(我们认为周期长度是 \(4\),X.
交替仅为美观)
XXXX....XXXX....XXXX....XXXX
[______________________]
[______________________]
我们不能询问相交的区间,怎么办办呢?你可能会想能不能递归判定呢?
不过这复杂度太高,我们必须要一个常数次询问的做法。
Observation 2:可以在最多 \(2\) 次操作的情况下判定一个长度是不是周期。
下给出构造方法:
XXXX....XXXX....XXXX....XXXX
[_____1____]
[_____2____]
[_____3____]
聪明的人类智慧告诉我们,我们可以先问 \(1,2\),再问 \(2,3\)。如果 \(1=2=3\),那么长度 \(4\) 就可以被判定了。
等于说我们用 \(1\),强行比较了原本相交的 \(2,3\)。我们知道了 \(2,3\) 这个的周期可以是长度 \(4\),然后我们又把前缀复制了一份丢在前面,那么长度 \(4\) 肯定也是周期。这样就完成了判定。
所以我们可以在最多 \(2\) 次操作的情况下判定一个长度是不是周期。
Observation 3:如果长度 \(a\) 是答案,\(b\) 也是答案,则 \(\gcd(a,b)\) 一定是答案。
根据周期的理论,我们可以得出它是真的。
所以说,我们初始令最短周期是 \(n\),尝试每次从小到大消除它的一个质因子即可。
这样复杂度是什么?设当前正在消除的质因子是 \(P\)。
我们也就要检测 \(\frac{n}{P}\) 是否是周期。
- 如果是,则 \(n \gets \frac{n}{P}\)。
- 如果不是,换下一个质因子。
Observation 4:上述算法时间查询次数不超过 \(\lfloor \log_3 16 \log_2 n \rfloor+4\)
我们发现 \(P=2\) 时只需要 \(1\) 次查询,\(P>3\) 时需要 \(2\) 次查询。
所以说我们结束这个算法至多需要 \(2\lfloor \log_3 n\rfloor\) 次询问。如果假设题目中的 \(n,m\) 同阶,则需要 \(4\lfloor \log_3 n\rfloor\) 次询问。
经过一番高中必修一(换底公式)数学推导,我们得到 \(4\lfloor \log_3 n \rfloor \le \log_3 16 \log_2 n\)。
<这里本该有张图>
其中红色的代表题目中的限制 \(3 \lfloor \log_2 n\rfloor+3\)。不难发现,只有当 \(n=1,2,3\) 的时候我们的算法可能会挂,但是发现由于我们的不等式是做了过剩估计的,其实并不会挂。
(至于怎么证 \(n\ne m\) 的呢,我不会)
Code
#include <bits/stdc++.h>
#define debug(...) fprintf(stderr ,__VA_ARGS__)
#define __FILE(x)\
freopen(#x".in" ,"r" ,stdin);\
freopen(#x".out" ,"w" ,stdout)
#define LL long long
const int MX = 1e3 + 23;
const LL MOD = 998244353;
int read(){
char k = getchar(); int x = 0;
while(k < '0' || k > '9') k = getchar();
while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
return x;
}
int qpow(int a ,int b){
int r = 1;
for(int i = 1 ; i <= b ; ++i) r *= a;
return r;
}
int qc;
int ask(int h ,int w ,int x1 ,int y1 ,int x2 ,int y2){
++qc;
// return 1;
printf("? %d %d %d %d %d %d\n" ,h ,w ,x1 ,y1 ,x2 ,y2); fflush(stdout);
return read();
}
int n ,m;
int getit(int a ,int x){
int r = 0;
for(int i = a ; i <= x ; i += a)
r += x % i == 0;
return r;
}
int main(){
n = read() ,m = read();
int a1 = m;
for(int i = 2 ; i <= m ; ++i){
int is_prime = true;
for(int j = 2 ; j * j <= i ; ++j){
if(i % j == 0) is_prime = false;
}
if(!is_prime) continue;
while(a1 % i == 0){
int tmp = a1 / i; // 看看长度为 tmp 行不行
// 分成 i 段了
int ok = 0;
#define id(x) (((x) - 1) * tmp + 1)
if(i == 2) ok = ask(n ,tmp ,1 ,id(1) ,1 ,id(2));
else ok = ask(n ,i / 2 * tmp ,1 ,id(1) ,1 ,id(i / 2 + 1))
&& ask(n ,i / 2 * tmp ,1 ,id(1) ,1 ,id(i / 2 + 2));
if(ok){
a1 /= i;
}
else{
break;
}
}
}
int a2 = n;
for(int i = 2 ; i <= n ; ++i){
int is_prime = true;
for(int j = 2 ; j * j <= i ; ++j){
if(i % j == 0) is_prime = false;
}
if(!is_prime) continue;
while(a2 % i == 0){
int tmp = a2 / i; // 看看长度为 tmp 行不行
// 分成 i 段了
int ok = 0;
#define id(x) (((x) - 1) * tmp + 1)
if(i == 2) ok = ask(tmp ,m ,id(1) ,1 ,id(2) ,1);
else ok = ask(i / 2 * tmp ,m ,id(1) ,1 ,id(i / 2 + 1) ,1)
&& ask(i / 2 * tmp ,m ,id(1) ,1 ,id(i / 2 + 2) ,1);
if(ok){
a2 /= i;
}
else{
break;
}
}
}
printf("! %d\n" ,getit(a1 ,m) * getit(a2 ,n)); fflush(stdout);
debug("query %d times %d %d\n" ,qc ,a2 ,a1);
return 0;
}