【题解】CCPC 2024 郑州站 C 中点
题目链接
题目大意
令初始集合为 \(S=\{(0, 0, ), (A, 0), (0, B), (A, B)\}\) 其中每个元素代表一个格点,每个格点的坐标都是整数,现允许通过以下操作扩展集合 \(S\) :
- 选择 \(P, Q \in S\), 若 \(P, Q\) 的中点也落在格点上时,把中点加入 \(S\) 。
现在问 \((X, Y)\) 是否能够被构造出来,如果可以,求出最少的步数与操作序列。
题目思路
二维的问题思考起来比较麻烦,我们可以现先考虑一维的,假设只有 \(X\) 这个维度。
显然,若 \(2^k | A\), 那么 \(A/2^k\) 这个点是可以构造出来的,否则不行。
若有解,考虑一个最大的 \(k\) , \(A/2^k | X\) 。因此,如果 \(X\) 可以被构造出来,那么 \(A/gcd(A, X) = 2^k\) ,基于此可以判断是否有解。
构造方法非常多,以下讲一种相对简单的。
要构造出 \({x_i}\),需要两个父节点,我们假定其中一个必须是 \(0\) 或 \(A\) ,记为 \(cx_{i-1}\) 那么:
\[x_{i} = \frac{cx_{i-1} + x_{i-1}}{2}
\]
即
\[x_{i-1} = 2x_{i} - cx_{i-1}
\]
当 \(2x_i <= A\) 时,\(cx_{i-1}\) 取 \(0\),否则得到负数没有意义;当\(2x_i > A\) 时,\(cx_{i-1}\) 取 \(A\) 否则超出范围没有意义。一路从 \(X\) 递推直到得到四个起始点中的一个,沿途记录 \(x_i\) 和 \(cx_{i}\),倒序输出即可。
显然,上述操作保证了不会经过重复经过一个点,一定可以收敛于某个角落节点,且路径是最短的。有关为什么会想到这么构造,我其实没想到,赛场上我评价为“我们少了一个关键结论”,于是分了27类讨论没写完。
AC代码
#include <iostream>
using namespace std;
int gcd(int a, int b) {
return b==0?a:gcd(b, a%b);
}
int ans[100][4];
int ncnt = 0;
int main() {
int a, b, x, y;
cin>>a>>b>>x>>y;
int ga = gcd(a, x);
int gb = gcd(b, y);
if(ga !=0 && __builtin_popcount(a / ga) != 1 || gb !=0 && __builtin_popcount(b / gb) != 1) {
cout<<-1<<endl;
return 0;
} else {
while(!((x == 0 || x == a)&&(y==0 || y==b))) {
int cx = x*2>a?a:0;
int cy = y*2>b?b:0;
x = x*2 - cx;
y = y*2 - cy;
ncnt++;
ans[ncnt][0] = x;
ans[ncnt][1] = y;
ans[ncnt][2] = cx;
ans[ncnt][3] = cy;
}
cout<<ncnt<<endl;
for(int i=ncnt;i>=1;i--) {
cout<<ans[i][0]<<" "<<ans[i][1]<<" "<<ans[i][2]<<" "<<ans[i][3]<<endl;
}
}
}
时间复杂度 \(O(log \ max(A, B))\)

浙公网安备 33010602011771号