题解:CF1552H Guess the Perimeter
闲话:写了一个基于一点点数论的做法,交上去过了,然后发现题解区没有,而且貌似做法更简单,遂写题解。
首先注意到,选点集的全集可以求出面积,这样就把问题转化成了
记矩阵水平于一行方向的边为长,另一边为宽。
不妨考虑一个看起来就很假的方法:选择一部分整行,答案与面积一定都是长的倍数,将得到的答案与面积取
令选择的与矩阵有交的行数量为
显然,这在
如何解决这个问题呢?
可以刻画这个方法正确的时候,选择的行可以长什么样。
注意到,当宽为奇数时,只要隔一行选一行就是对的。
证明:设矩形宽为
利用定理
-
若
b=2w-1 ,\gcd(w,b)=\gcd(w,2w-1)=\gcd(w,w-1)=1 。 -
若
b=2w+1 ,\gcd(w,b)=\gcd(w,2w+1)=\gcd(w,w+1)=1 。
证毕。
当宽为偶数怎么办呢?
注意到当宽为偶数时,可以把原问题进行放缩,即删掉所有未被选择的行,把这一次的答案当成新矩形的面积,转化成子问题,这样对正确性一定不会有影响。
在我们不知道宽奇偶性的情况下,也就是对于步长为
因为
这样类似于一个倍增,次数是
然而题目要求
我们刚才倍增每次查的其实是相互独立的,并且注意到答案具有单调性,即只需要二分出最后一个答案不为
加上一开始查的面积,一共是
核心代码:
namespace Junounly
{
int ask(int x)
{
cout<<"? "<<' '<<200*(200/x)<<endl;
for(int i=x;i<=200;i+=x)
for(int j=1;j<=200;j++)
cout<<i<<' '<<j<<' ';
cout<<endl;
int res;
cin>>res;
return res;
}
void main()
{
int sum=ask(1),l=0,r=7,res=114514;
for(;l<r;)
{
int mid=(l+r+1)>>1,now=ask(1<<mid);
if(now) l=mid,res=now;
else r=mid-1;
}
int A=114514;
if(res) A=__gcd(res,sum);
else A=1;
cout<<"! "<<(A+sum/A-2)*2<<endl;
}
}