先看第一题,有n*m个点,求在这些点中,有多少条直线,经过了至少两点,且不是水平的也不是竖直的。

  分析:由于对称性,我们只要求一个方向的线即可。该题分成两个过程,第一个过程是求出n*m的矩形中,dp[i][j]代表在这个矩形中终点是到(i,j)这个点的满足题意的直线条数,那么,用dp的话就可以得出递推关系:由长和宽分别小1的左右两个矩形中满足题意的线的条数减去他们共有的矩形中满足的线的条数(容斥减去重复部分),之后还要判断从最左上角的点(1,1)到(i,j)是否可以组成一条线,这个条件是gcd(i,j)是否等于1。

  之后第二个过程就是递推答案了,设ans[i][j]表示在这个矩形中满足题意的条数,那么同样的,可以由上面的容斥来递推,同时,还要加上这个矩形内到(i,j)这个点满足的条数,另外还要减去一半规模大小的到这个点的线的条数,因为如果(i,j)为(6,8),那么一半规模下,(1,1)到(3,4)这个点的线和到(6,8)这条线是重复的。

  这样就做完了题目(最后不要忘了乘以2)。具体见代码:

 1 #include <stdio.h>
 2 #include <algorithm>
 3 #include <string.h>
 4 #include <iostream>
 5 #include <vector>
 6 #include <queue>
 7 using namespace std;
 8 typedef long long ll;
 9 const int N = 300+5;
10 
11 int dp[N][N];
12 int ans[N][N];
13 
14 int gcd(int a,int b) {return a%b?gcd(b,a%b):b;}
15 
16 void init()
17 {
18     for(int i=1;i<N;i++)
19     {
20         for(int j=1;j<N;j++)
21         {
22             dp[i][j] = dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1] + (gcd(i,j)==1);
23         }
24     }
25     for(int i=1;i<N;i++)
26     {
27         for(int j=1;j<N;j++)
28         {
29             ans[i][j] = ans[i-1][j] + ans[i][j-1] - ans[i-1][j-1] + dp[i][j] - dp[i>>1][j>>1];
30         }
31     }
32 }
33 
34 int main()
35 {
36     init();
37     int n,m;
38     while(scanf("%d%d",&n,&m)==2)
39     {
40         if(n==0 && m==0) break;
41         printf("%d\n",2*ans[n-1][m-1]);
42     }
43 }

 

  做出了这题,第二题就是类似的了。先在所有的点中枚举出选3个点的可能性,然后,减去一条水平或者竖直线上重复的,再减去同在一条斜线上重复的即可。相当类似,具体见代码吧:

 1 #include <stdio.h>
 2 #include <algorithm>
 3 #include <string.h>
 4 #include <iostream>
 5 #include <vector>
 6 #include <queue>
 7 using namespace std;
 8 typedef long long ll;
 9 const int N = 1000+5;
10 
11 ll dp[N][N];
12 ll ans[N][N];
13 
14 int gcd(int a,int b) {return a%b?gcd(b,a%b):b;}
15 
16 void init()
17 {
18     for(int i=1;i<N;i++)
19     {
20         for(int j=1;j<N;j++)
21         {
22             dp[i][j] = dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1] + (ll)(gcd(i,j)-1);
23         }
24     }
25     for(int i=1;i<N;i++)
26     {
27         for(int j=1;j<N;j++)
28         {
29             ans[i][j] = ans[i-1][j] + ans[i][j-1] - ans[i-1][j-1] + dp[i][j];
30         }
31     }
32 }
33 
34 ll C(ll x) {return x*(x-1)*(x-2)/6;}
35 
36 int main()
37 {
38     init();
39     int n,m;
40     int cnt = 1;
41     while(scanf("%d%d",&n,&m)==2)
42     {
43         if(n==0 && m==0) break;
44         ll Ans = C((n+1)*(m+1)) - (n+1)*C(m+1) - (m+1)*C(n+1);
45         Ans -= 2*ans[n][m];
46         cout<<"Case "<<cnt++<<": "<<Ans<<endl;
47     }
48 }

  

  但是,这两题都要注意的地方是,递推dp时三个矩形都是在右下角的(因为向下或者向右平移一个单位的话条数是不变的),这样递推起来的话只要再考虑从(1,1)这个点到(i,j)这个点的情况即可;而递推ans的时候,矩形是偏左上方的,那么,只要再加上整个大矩形内到(i,j)这个点的情况即可。当然,纯属个人理解。