洛谷 CF1552H. Guess the Perimeter

就你?这是 3300 的题??

记录了一些非正解想法和思考过程。


首先把格子转为点,最后再去掉四个角的周长。

很显然的做法是:可以枚举每一行,查询一整行的格子,答案只会是 $0$ 或者长度,枚举列同理,这样询问次数最坏是 $400$ 次。

考虑到这是一个网格,所以考虑对格子黑白染色的常规套路,但这显然并没有什么用。
考虑分治,每次把网格横着切成两半,如果上一半有矩形的一部分就往上递归分治,否则往下分治,但是这样的询问次数是两个 $\log$ 的。
但是显然我们可以先查询全局得到面积,这样只需要求长度,即可相除得到宽度,就变成了一个 $\log$ 外加一次查询。

分治并不好再往下优化了,但是黑白染色或许有一些出路?
把对格子的染色改成对行的染色,即每隔一行选一行的格子询问。
假设得到的答案为 $x$,那么没有被询问到格子数量为 $S-x$,只要满足 $x \not= S-x$,那么显然答案就是 $|(S-x)-x|$,即两部分之差。
但是 $x=S-x$ 怎么办?这样矩形的宽度就是偶数了,没法再确定。

宽度是偶数?把宽度除以二再查询就解决了。
也就是每隔 $2^i$ 行选择 $2^i$ 行查询,以 $2^i$ 行为一个单位进行黑白染色!
这样看似可行,但是问题在于,如果最后一个 $2^i$ 行切到了矩形的底部,你并不知道在矩形外的有多少行,也就无法计算。


上述做法不能成立的原因主要是 $2^i$ 为一个单位不知道切到矩形内部多少行,那么改成 $1$ 行就没有这个问题了!
考虑每 $2^i$ 内部选择第一行,即每选一行中间隔 $2^i-1$ 行再选,这样从小往大枚举 $i$ 直到出现类似于 $x \not= S-x$ 的情况。
把上面的情况扩展,如果有 $x \times 2^i = S$,那么就 $i + 1 \to i$。
否则 $x \not= S - x$,得到宽度为 $|\frac{S}{2^{i-1}}-2x|$。


这样虽然询问次数和分治一样还是一个 $\log$ 外加一次查询,但是 $2^i | S$ 是有单调性的!
因此可以二分 $i$,注意到这样的询问次数是 $\log \log 200 + 1 = 4$,刚好满足题目限制。

压行大师,哈哈,最喜欢这种思维层层递进代码又不难写的题了。

#include <bits/stdc++.h>
using namespace std;
int ans[210];
int query(int p) {
    printf("? %d\n", (199 / p + 1) * 200);
    for (int i = 1; i <= 200; i += p) for (int j = 1; j <= 200; j++) printf("%d %d ", i, j); puts("");
    cout.flush(); scanf("%d", &ans[p]); return ans[p];
}
int main() {
    int l = 1, r = 8, S = query(1);
    while (l < r) {
        int mid = l + r >> 1, x = query(1 << mid);
        if ( (x << mid) ^ S) r = mid;
        else l = mid + 1;
    }
    int a = abs( ( S >> (l - 1) ) - (ans[1 << l] << 1) ), b = S / a;
    printf("! %d\n", (a + b) * 2 - 4);
    return 0;
}
posted @ 2025-03-28 20:32  Conan15  阅读(8)  评论(0)    收藏  举报  来源