题意:给你一个H,一个M,一个A。一天有h个小时,一小时有m分钟,问你一天之内有多少时刻(分钟),时针和分针的角度差小于等于 2 π A H M \frac{2 \pi A}{HM} HM2πA.
这题开场十三分钟就有队伍过了,导致我们以为是个铜牌思维题(其实最后也只过了四五十个队),签完到就去开了这个题。如果知道类欧确实过的很快,如果不知道的话,可能就会像我们一样推出个 O ( N ) O(N) O(N)的式子,有一种能做但是又会tle的没准再想想就能想出来的错觉。和队友开了三个小时的这题导致阅读理解签到一直被耽搁最后差六分钟罚时被拽出铜牌区。收获人生第一次打铁。(当然希望也是最后一次了
沈阳忏悔录都写了不下三遍了,讲讲这题怎么做好了。讲道理还是自己太菜了,算法学的不够多,不然这题大概也算板子题了,不应该丢的。当事人:现在就是非常的后悔.jpg。
首先考虑,对于每一分钟,分针会转 2 π H H M \frac{2 \pi H }{HM} HM2πH 角度,而时针会转 2 π H M \frac{2 \pi }{HM} HM2π 角度 , 多少分钟满足二者之差小于等于 2 π A H M \frac{2 \pi A}{HM} HM2πA的角度。
题目转换为,有多少数对 ( k , i ) (k,i) (k,i)满足
i H M − A ≤ k ( H − 1 ) ≤ i H M + A iHM-A≤k(H-1)≤iHM+A iHM−A≤k(H−1)≤iHM+A.
其中 0 ≤ k ≤ H M 0≤k≤HM 0≤k≤HM 且k和i均为整数.
把 i i i 的范围搞出来,就可以用类欧几里得的板子了。类欧几里得可以在 O ( l o g n ) O(logn) O(logn)时间内快速计算出 F ( a , b , c , n ) = ∑ i = 0 n F(a,b,c,n)=\sum_{i=0}^n F(a,b,c,n)=∑i=0n ⌊ \lfloor ⌊ a i + b c \frac{ai+b}{c} cai+b ⌋ \rfloor ⌋.
也就是说,可以理解为一条直线与 x = 0 x=0 x=0, x = n x=n x=n, y = 0 y=0 y=0三条线构成的梯形中的整点数对的个数。
需要注意的是,这个 i i i 的下标是从0开始的,而我们要计算的式子两个端点比较特殊,需要拿出来单独计算。所以实际上算的应该是 i i i 在[1,H-2]范围内的答案。这里需要一个高中知识来转换一下。就是左加右减。将整个要计算的函数左移一位,然后令 n=H-3 ,再加上两个端点单独计算的就可以啦。
其实是因为题目说了A是可以等于 H M 2 \frac{HM}{2} 2HM的,这样在每个区间的交界处是有可能发生重复的。所以需要特判一下,ans和HM取个min就好。
板子来源
AC代码:
#include
#include
#define ll long long
#define int long long
using namespace std;
ll f(ll a, ll b, ll c, ll n)
{
if(!a) return b/c*(n+1);
if(a>=c||b>=c)
return f(a%c,b%c,c,n)+(a/c)*n*(n+1)/2+(b/c)*(n+1);
ll m=(a*n+b)/c;
return n*m-f(c,c-b-1,a,m-1);
}
signed main( )
{
int h,m,a;
scanf("%lld%lld%lld",&h,&m,&a);
int ans1=f( h*m ,a+h*m, h-1 , h-3 );
int ans2=f( h*m ,-a+h*m-1, h-1 , h-3 );
int ans=ans1-ans2;
ans=ans +1 + a/(h-1) ;
ans=ans + a/(h-1) +1;
ans--;
ans=min(ans,h*m);
printf("%lld\n",ans);
return 0;
}