格点统计问题(UVALive 3295,UVALive 3720)

题目链接

UVALive 3720
UVALive 3295

大意

第一题是求 nXm 的格点中有多少条斜线(至少经过两个点).第二个问题求的是有多少个各点组成的三角形.
我先说说题解,然后再总结其异同.

分析

第一题我们可以这样办:由对称性我们知道,”\”方向的斜线与”/”方向的斜线总数相同,因此只需求一边的就行.我们可以把问题拆分一下,
我们先求出从(0,0)点到(i,j)这个矩形区域内(经过(0,0))的直线条数,设为 dp[i][j] ,这是能够递推的,

dp[i][j]=dp[i1][j]+dp[i][j1]dp[i1][j1]+(i,j)

dp[i][j] 的定义,我们知道只需要判断(i,j)是否能与(0,0)构成一条新的直线即可.因此由简单的几何思想我们知道只需要判,断 gcd(i,j)==1 ,因此

dp[i][j]=dp[i1][j]+dp[i][j1]dp[i1][j1]+(gcd(i,j)==1)

接下来我们来求(0,0,)->(i,j)区域内总的方案数(ans),递推关系显然成立
sum[i][j]=sum[i1][j]+sum[i][j1]sum[i1][j1]+(i,j)

由对称性从一个矩形区域左上角到其他点的直线条数显然等于从右下角到其他点的直线条数,而新增贡献就是(i,j)到区域其他点的贡献数,不过要注意,这样做会被重复计数,举个例子(0,0)->(1,1),一定与(0,0)->(2,2)重复.但是这个从副数目到底为多少呢?我们可以想象至少要矩形长宽均扩大两倍才会重复,因此新增贡献为
dp[i][j]dp[i/2][j/2]

可以这样想计算到 (sum[i][j]) 的时候 dp[i/2][j/2] 肯定已经加过了,而由我们之前的定义

dp[i][j] :从(0,0)点到(i,j)这个矩形区域内(经过(0,0))的直线条数,设为 dp[i][j]

因此这些直线必定会包含在 (dp[i][j]) 中.

AC code

#include 

using namespace std;
typedef long long LL;


const int maxn = 1000+10;


LL dp[maxn][maxn];
LL sum[maxn][maxn];

LL comb_3(LL n){
    if(n<0)return n;
    return n*(n-1)/2*(n-2)/3;
}

void init(){
  memset(dp,0,sizeof(dp));
  memset(sum,0,sizeof(sum));
  for(int i=1 ; ifor(int j =1 ; j1][j]+dp[i][j-1]-dp[i-1][j-1]+(__gcd(i,j)-1);
  }
  for(int i=1 ; ifor(int j = 1  ; j1][j]+sum[i][j-1]-sum[i-1][j-1]+dp[i][j];
}

int n,m;

int main(int argc, char const *argv[]) {
    init();
    int kase = 0;
    while (scanf("%d%d",&n,&m ) && n) {
        LL ans = comb_3((n+1)*(m+1));
        ans-=comb_3(n+1)*(m+1);
        ans-=comb_3(m+1)*(n+1);
        ans-=sum[n][m]*2;
        printf("Case %d: %lld\n",++kase,ans );
    }

    return 0;
}

UVALive 3720

这一题是问有多少个格点三角形.
我们很容易想到从反面来解决的思想,用总的三个点的总数来减去共线三点的总数,由于直线很好统计,难得是斜线。同样的,我们采用上面的方法, ans[i][j] 表示矩形区域 (0,0)->(i.j)内的共线的三点数.由前面的方法我们知道

ans[i][j]=ans[i1][j]+ans[i][j1]ans[i1][j1]+((i,j)线)

dp[i][j] :矩形区域内与(0,0)共线的数目.

dp[i][j]=dp[i1][j]+dp[i][j1]dp[i1][j1]+gcd(i,j)1

由前面的内容我们知道,(i,j)加入之后对dp[i][j]的影响为(gcd(i,j)-1)。与(i,j),(0.0)共线的点为(gcd(i,j)+1),选第三个点共(gcd(i,j)+1-2)种情况.
因此
ans[i][j]=ans[i1][j]+ans[i][j1]ans[i1][j1]+dp[i][j]

AC code

#include 

using namespace std;
typedef long long LL;


const int maxn = 1000+10;


LL dp[maxn][maxn];
LL sum[maxn][maxn];

LL comb_3(LL n){
    if(n<0)return n;
    return n*(n-1)/2*(n-2)/3;
}

void init(){
  memset(dp,0,sizeof(dp));
  memset(sum,0,sizeof(sum));
  for(int i=1 ; ifor(int j =1 ; j1][j]+dp[i][j-1]-dp[i-1][j-1]+(__gcd(i,j)-1);
  }
  for(int i=1 ; ifor(int j = 1  ; j1][j]+sum[i][j-1]-sum[i-1][j-1]+dp[i][j];
}

int n,m;

int main(int argc, char const *argv[]) {
    init();
    int kase = 0;
    while (scanf("%d%d",&n,&m ) && n) {
        LL ans = comb_3((n+1)*(m+1));
        ans-=comb_3(n+1)*(m+1);
        ans-=comb_3(m+1)*(n+1);
        ans-=sum[n][m]*2;
        printf("Case %d: %lld\n",++kase,ans );
    }

    return 0;
}

总结

  1. 对称转化,左上角为参考点与右下角为参考点的转化
  2. 递归思考.满足和的性质只需考虑(i,j)的加入的影响.
  3. 简单化子问题.从最终答案递推到需要求解的问题上去.

你可能感兴趣的:(算法&数据结构)