UVALive3720

题目大意:见刘汝佳《算法竞赛入门经典——训练指南》P173。

解题思路:

  问题可以转化为求共有多少条过点阵中的点的斜线。其中必定包含左斜线和右斜线,由于点阵式对称的,所以我们只需求出左右斜线中的一种的总数,乘2就可以得到答案。

  我们先求出各点到其左上角的只经过两个点的左斜线的总数 cnt ,那么答案就是所有点的 cnt 的总和去掉其中重复计算的数值。设点阵上某点坐标为(i,j),则这个子问题可以转化为求 [ 1,i ] 和 [ 1,j ] 中互质的数对的个数。简单解释:对于点(i,j),若 gcd(i,j)=1,则该点到(0,0)的直线必定只经过此二点;否则你必定可以找到 x=i/gcd(i,j),y=j/gcd(i,j),(i,j)到(0,0)的直线经过(x,y)。而对于该点到左上角其他点(除了(0,0))的只经过两个点的直线,可以通过 [ 1,i ) 和 [ 1,j )中各点到(0,0)的只经过两个点的直线平移得到(如图1所示)。递推式为:cnt[i][j] = cnt[i-1][j] + cnt[i][j-1] - cnt[i-1][j-1] + (gcd(i,j)==1?1:0)。

  现在来算最终答案 ans 。很自然的得出:ans[i][j] = ans[i-1][j] + ans[i][j-1] - ans[i-1][j-1] + cnt[i][j]。但这么计算会有一个重复:在 cnt[i][j] 中包含了 cnt[i/2][j/2] ,所以要再减去 cnt[i/2][j/2]。over.

AC代码:

 1 #include <cstdio>
 2 using namespace std;
 3 const int maxn=302;
 4 int cnt[maxn][maxn],ans[maxn][maxn];
 5 int gcd(int a,int b){
 6     if (b == 0)    return a;
 7     return gcd(b, a%b);
 8 }
 9 void init(){
10     for(int i=1;i<maxn;i++){
11         for(int j=1;j<maxn;j++){
12             cnt[i][j]=cnt[i-1][j]+cnt[i][j-1]-cnt[i-1][j-1]+(gcd(i,j)==1?1:0);
13         }
14     }
15     for(int i=1;i<maxn;i++){
16         for(int j=1;j<maxn;j++){
17             ans[i][j]=ans[i-1][j]+ans[i][j-1]-ans[i-1][j-1]+cnt[i][j]-cnt[i/2][j/2];
18         }
19     }
20 }
21 int main(){
22     init();
23     int n,m;
24     while(scanf("%d%d",&n,&m)==2&&n&&m){
25         printf("%d\n",ans[n-1][m-1]*2);
26     }
27     return 0;
28 }

 

posted @ 2017-10-07 17:52  Blogggggg  阅读(105)  评论(0编辑  收藏  举报