【Codeforces 449E】Jzzhu and Squares
数论,推式子
题目大意:
给定 $n\times m$ 的网格图,求所有的四个顶点都是整点的正方形中,包含的完整的 $1\times 1$ 的方格的总个数。
多组数据。
题解:
以下都默认 $n\leq m$
令 $f(n)$ 表示 $n\times n$ 的网格中,所有四个顶点都在网格边缘的正方形包含的完整小方格个数。
那么有 $ans=\sum\limits_{i=1}^n (n-i+1)(m-i+1)f(i)$。
考虑如何求 $f(n)$。
四个顶点都在 $n\times n$ 的网格的角落时,产生 $n^2$ 的贡献。考虑均不在角落的情况。如图所示。
其中,红色的正方形(包括紫色部分)是我们当前在考虑贡献的正方形。我们将其分成紫色的小正方形和四个红色的三角形的贡献分别考虑。
令当前三角形的一条边为 $x$,则另一条边为 $n-x$。
那么中间的正方形的边长就为 $|n-2x|$,贡献为 $(n-2x)^2$。
考虑四个直角三角形的贡献。
以左上角的红色三角形为例,我们发现,一个恰好在三角形内部或者斜边上的点,对应着一个图形内部的完整小正方形,这个小正方形的左上角就是这个点。
所以相当于求一个三角形内部的点和斜边上的点的总个数。
根据皮克定理,设多边形内部的点有 $a$ 个,恰在边上的点有 $b$ 个,那么所有顶点都是整点的多边形的面积为 $a+\frac b 2-1$。
我们令 $g(n,m)$ 表示两条直角边为 $n$ 和 $m$ 的直角三角形的贡献。
那么其边上的点的个数为 $\gcd(n,m)+n+m$,所以内部的点的个数为 $\frac{nm+2-\gcd(n,m)-n-m}2$。而边上的点的个数(不含两端)为 $\gcd(n,m)-1$。
因此 $g(n,m)=\frac{nm-n-m-\gcd(n,m)}{2}$
接下来推 $f(n)$。
其中 $h(n)=\sum\limits_{i=1}^n\gcd(i,n-i)$.
上面的每个求和部分都可以前缀和预处理,考虑如何求 $h(n)$。
所以线性筛,然后狄利克雷卷积即可。时间复杂度 $O(n\log n)$。
现在 $f(n)$ 求出来了。
但是多组数据,所以我们需要 $O(1)$ 求答案。
而 $\sum\limits_{i=1}^n(n-i+1)^2f(i)$ 和 $\sum\limits_{i=1}^n (n-i+1)f(i)$ 都可以线性预处理。
因此可以做到 $O(1)$ 单次查询。
Code:
#include<iostream>
using namespace std;
const int N=1e6+5,md=1e9+7,n=1e6+1;
typedef long long LL;
int F[N],S[N],T[N],P[N];
int phi[N],h[N],pr[N],tot,pri[N/10],pf[N];
bool vis[N];
inline void upd(int&a){a+=a>>31&md;}
inline int calc(int x){return((x+1LL)*x>>1)%md;}
void init(){
phi[1]=1;
for(int i=2;i<=n;++i){
if(!vis[i])pri[++tot]=i,phi[i]=i-1;
for(int j=1;j<=tot&&i*pri[j]<=n;++j){
vis[i*pri[j]]=1;
if(i%pri[j]==0){
phi[i*pri[j]]=phi[i]*pri[j];
break;
}
else
phi[i*pri[j]]=phi[i]*(pri[j]-1);
}
}
for(int i=1;i<=n;++i)
for(int j=i;j<=n;j+=i)
h[j]=(h[j]+(LL)i*phi[j/i])%md;
pf[1]=1,pf[2]=5;
pr[1]=1,pr[2]=4;
for(int i=3;i<=n;++i){
pr[i]=(pr[i-2]+(LL)i*i+(i-2LL)*(i-2))%md;
pf[i]=(pf[i-1]+(LL)i*i)%md;
}
}
int main(){
init();
for(int i=1;i<=n;++i)
F[i]=(pr[i]+2LL*i*calc(i-1)-2LL*pf[i-1]+4LL*md-2LL*i*(i-1)%md+2LL*h[i]-2*i)%md;
for(int i=1,s=0;i<=n;++i)S[i]=((LL)S[i-1]+s+F[i])%md,s=(s+2LL*F[i])%md;
for(int i=1,t=0;i<=n;++i)P[i]=((LL)P[i-1]+t+F[i])%md,upd(t+=F[i]-md);
for(int i=1;i<=n;++i)upd(T[i]=T[i-1]+S[i]-md);
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int t;
for(cin>>t;t--;){
int n,m;
cin>>n>>m;
if(n>m)n^=m^=n^=m;
int ans=(T[n]+P[n]*(LL)(m-n))%md;
cout<<ans<<'\n';
}
return 0;
}