这是一道很综合的计数问题,对于思维的全面性,解法的过渡性,代码能力,细节处理,计数问题中的各种算法,像gcd、容斥、类欧几里德算法都有考察.
在省选模拟赛中做到了这题,然而数据范围是n,m小于等于1000.
首先有一个O(n^4m^4)的暴力.
然后开始计数,思路是:答案等于任取4个点的方案数+2*取4个点不为凸的方案.
前一部分相对来说容易统计,先用组合数算所有的,再把存在3点、4点共线的矩形的贡献减掉就好了.
这里用到了矩形框的思路,利用了容斥,而且在计数的时候用gcd作为工具,这个思路下面还会用到,这一部分的具体实现见代码.
后一部分,对我来说,我觉得是十分困难的,我经历了从O(n^2m^2)到O(n^2m),再到O(nmlog)的历程.
先从最基本的计数方向考虑,我们怎么统计取4个点不为凸的方案.
答案就是找到所有三角形,算出其内部点的和.
那么我们找所有三角形,继续沿用上面矩形框的思路,我们枚举最小的能把三角形框起来的矩形框.
然后我分了几种情况:

(以下所说的占有的顶点均指矩形的顶点,所说的占有顶点的三角形均满足其所有顶点都在矩形框上,设矩形的某一对平行边为长,另一对平行边为宽)
  I.占有三个顶点的三角形
  II.占有一个顶点的三角形
  III.四种占有两个顶点的三角形
    1.一边为对角线,另一顶点在宽上的三角形
    2.一边为对角线,另一顶点在高上的三角形
    3.以宽为底,另一顶点在对面边上的三角形
    4.以长为底,另一顶点在对面边上的三角形
  IV.一边为对角线一点在矩形内部的三角形(我用一晚上查错,才发现我漏掉了这种情况)

在计算其内部点的个数的时候,需要用到pick定理.
对于以上情况的计数,我采用的方法都是,先固定一个或两个顶点,算其贡献,然后再算由于对称性而产生的贡献.
这样我就想到了一种O(n^2m^2)的做法:

先O(nm)枚举矩形框,然后再O(1)计算I,O(nm)计算II(假定其一顶点为(0,0),另外两顶点在与(0,0)相对的边上滑动),O(n+m)计算III里的四种(与计算II的思路相似),O(nm)计算IV(枚举内部点作为顶点).
面积直接计算,沿边点数用gcd计算.

具体实现见代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
typedef long long LL;
const int P=1000000007;
const int N=1010;
int gcd[N][N];
inline int GCD(int x,int y){
  return gcd[x][y]?gcd[x][y]:gcd[x][y]=(x==0?y:GCD(y%x,x));
}
inline void Init(){
  register int i,j;
  for(i=0;i<=1000;++i)
    for(j=0;j<=1000;++j)
      gcd[i][j]=GCD(i,j);
}
inline int count_all(int n,int m){
  int cnt=(n+1)*(m+1);
  int ret=41666667LL*cnt%P*(cnt-1)%P*(cnt-2)%P*(cnt-3)%P;
  register int i,j,tmp,sum;
  for(i=0;i<=n;++i)
    for(j=!i;j<=m;++j){
      tmp=gcd[i][j]-1;
      if(!tmp)continue;
      sum=(n-i+1)*(m-j+1)*(i&&j?2:1);
      ret=(ret-(LL)tmp*sum*(cnt-3)+P)%P;
      if(tmp==1)continue;
      ret=(ret+(LL)tmp*(tmp-1)/2%P*sum%P*3%P)%P;
    }
  return ret;
}
inline int count_rest(int n,int m){
  int ret=0,sum,s,tmp;
  register int i,j,x,y,temp;
  for(i=1;i<=n;++i)
    for(j=1;j<=m;++j){
      sum=(n-i+1)*(m-j+1);
      s=i*j+2;
      tmp=(s-j-i-gcd[i][j])*2;
      for(x=1;x<i;++x)
        for(y=1;y<j;++y){
          temp=s-x*y-(gcd[x][j]+gcd[i][y]+gcd[i-x][j-y]);
          tmp=(tmp+temp*2)%P;
        }
      x=i;
      for(y=1;y<j;++y){
        temp=s-x*y-(gcd[x][j]+gcd[i][y]+gcd[i-x][j-y]);
        tmp=(tmp+temp*2)%P;
      }
      y=j;
      for(x=1;x<i;++x){
        temp=s-x*y-(gcd[x][j]+gcd[i][y]+gcd[i-x][j-y]);
        tmp=(tmp+temp*2)%P;
      }
      x=0;
      for(y=1;y<j;++y){
        temp=s-x*y-(gcd[x][j]+gcd[i][y]+gcd[i-x][j-y]);
        tmp=(tmp+temp)%P;
      }
      y=0;
      for(x=1;x<i;++x){
        temp=s-x*y-(gcd[x][j]+gcd[i][y]+gcd[i-x][j-y]);
        tmp=(tmp+temp)%P;
      }
      for(x=1;x<i;++x)
        for(y=1;y<j;++y){
          if(y*i-x*j<=0)continue;
          temp=y*i-x*j+2-(gcd[x][y]+gcd[i-x][j-y]+gcd[i][j]);
          tmp=(tmp+temp*2)%P;
        }
      ret=(ret+(LL)tmp*sum)%P;
    }
  return ret;
}
inline int calc(int n,int m){
  if((n+1)*(m+1)<4)return 0;
  return (count_all(n,m)+2LL*count_rest(n,m))%P;
}
int main(){
  Init();
  int n,m;
  scanf("%d%d",&n,&m);
  printf("%d\n",calc(n,m));
  return 0;
}
Kod

相关文章:

  • 2021-10-01
  • 2021-05-22
  • 2022-02-09
  • 2021-09-05
  • 2021-07-28
  • 2021-08-03
  • 2022-02-02
  • 2021-11-15
猜你喜欢
  • 2022-12-23
  • 2022-12-23
  • 2021-06-07
  • 2022-12-23
  • 2022-12-23
  • 2021-12-01
  • 2021-07-17
相关资源
相似解决方案