首先一个显而易见的GCD性质是当且仅当
时两点连线不经过其他点。我们设数组
表示
到
之间的合法直线个数,如下图所示:
根据二维前缀和的知识,得到:
实际上我们计算答案时需要的是 到其他点的合法直线个数,这并不难,对称过去实际上和上述结果一样的
但是这样并不是最后的答案,使用二维前缀和优化dp,那么答案显然需要预处理出的前缀和来计算,假设当前点是 ,只需要知道三个点的 值便可求出所有的情况:
即之前的所有情况
再加上现在的所有情况
,但是这样显然有一些直线是重复的,需要减去
,为什么?因为重复的直线肯定是
,因为大于1的最小因数是
,因此只需除以2便可计算出所有的重复直线,以
为例,如下图所示:
那么最后的答案就是:
ps:题目给的 是点的个数而不是线段长度,因此答案为
#include <bits/stdc++.h>
#include <unordered_map>
#include <unordered_set>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define ins insert
#define Vector Point
#define lowbit(x) (x&(-x))
#define mkp(x,y) make_pair(x,y)
#define mem(a,x) memset(a,x,sizeof a);
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef pair<double,double> pdd;
const double eps=1e-8;
const double pi=acos(-1.0);
const int inf=0x3f3f3f3f;
const double dinf=1e300;
const ll INF=1e18;
const int Mod=1e9+7;
const int maxn=305;
int gcd[maxn][maxn];
ll d[maxn][maxn],ans[maxn][maxn];
void init(){
for(int i=1;i<maxn;i++)
for(int j=1;j<maxn;j++) if(!gcd[i][j])
for(int k=1;i*k<maxn && j*k<maxn;k++)
gcd[i*k][j*k]=k;
for(int i=1;i<maxn;i++)
for(int j=1;j<maxn;j++)
d[i][j]=d[i-1][j]+d[i][j-1]-d[i-1][j-1]+(gcd[i][j]==1?1:0);
/*for(int i=1;i<maxn;i++){
for(int j=1;j<maxn;j++)
cout<<d[i][j]<<" ";
cout<<endl;
}*/
for(int i=1;i<maxn;i++)
for(int j=1;j<maxn;j++)
ans[i][j]=ans[i][j-1]+ans[i-1][j]-ans[i-1][j-1]+d[i][j]-d[i/2][j/2];
}
int main(){
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
ios_base::sync_with_stdio(0),cin.tie(0),cout.tie(0);
init();
int n,m;
while(cin>>n>>m && n){
cout<<ans[n-1][m-1]*2<<"\n";
}
return 0;
}