[NOIP2017模拟]弹球

来源:互联网 发布:php时间戳精确到毫秒 编辑:程序博客网 时间:2024/06/16 04:53

2017.10.19 T2 1993

样例数据
输入

5
4 6
4 7
5 6
5 7
9 15

输出

8
7
9
11
39

分析:举个例子,样例中4*6的情况,黄色代表只染色一次,橙色表示染色两次(显然不可能染色三次,除非你死循环了,但是根据对称性由于你出发于角落所以你无论如何都会最终打到角落,所以不可能有路径走两次及以上):
这里写图片描述
从这幅图我反正什么都看不出来。有人再分析了一下,发现染色两次的格子的数量等于打到两侧的次数(除了起点、终点)乘以打到上下边的次数(除了起点终点)的一半(我也是听了讲才“发现”了这个规律……)。

现在我们把图横向展开,也就是不让它弹回去,变成了这样(这个图的黄色、橙色不是染色次数,只是为了方便看):
这里写图片描述
可以看出,除去起点列,每次回弹时都向右移动了3格(也就是4-1),而一共向右移动了15格,所以上下碰壁15/3=5次,对于左右碰壁,15=(6-1)*3,所以左右碰壁3次(看原图也知道,除去起点,每次左右碰壁回弹时都移动了5格,你也可以想象如果将图纵向展开,效果应该和横向展开一样,就是每次回弹都向下移动了5格[也就是6-1])。所以,弹球一共走过的格子数就是长宽各减1的最小公倍数+1(加上起点)。现在我们要删去走过两次的格子。与上下相碰的次数(除了起点、终点)也就是展开后上下相碰的次数,如图就是5-1=4(减去终点),同理与左右相碰的次数就是3-1=2(也是减去终点)。每个走过两次的点要减两次才能去掉,正好和上面的结论那个“一半”抵消了,所以直接就是2*4=8,答案便是15+1-8=8。
其实第二个规律不用那么复杂,只需要把路径从格子中移动到点上直接就可以看出来了。
啊,这规律真难找呵!

代码
推出规律后代码简单得一匹

#include<iostream>#include<cstdio>#include<cstdlib>#include<cstring>#include<string>#include<ctime>#include<cmath>#include<algorithm>#include<cctype>#include<iomanip>#include<queue>#include<set>using namespace std;int getint(){    int sum=0,f=1;    char ch;    for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());    if(ch=='-')    {        f=-1;        ch=getchar();    }    for(;isdigit(ch);ch=getchar())        sum=(sum<<3)+(sum<<1)+ch-48;    return sum*f;}int gcd(int x,int y){    int m;    while(x%y)    {        m=y;        y=x%y;        x=m;    }    return y;}long long T,n,m,x,ans;int main(){    freopen("ball.in","r",stdin);    freopen("ball.out","w",stdout);    T=getint();    while(T--)    {        ans=0;        n=getint(),m=getint();        x=gcd(n-1,m-1);//求最大公约数,由性质lcm(x,y)=x*y/gcd(x,y)求最小公倍数        ans+=(n-1)*(m-1)/x+1;//加上最小公倍数和起点,也就是弹球总路程        ans-=(ans/(n-1)-1)*(ans/(m-1)-1);//减去如分析所述的染色两次的点        cout<<ans<<'\n';    }    return 0;}

本题结。