排列组合

先补充一个其它的东西—–二项式定理:
这里写图片描述

排列:

定义:从n个不同元素中,任取m(m≤n,m与n均为自然数,下同)个元素按照一定的顺序排成一列,叫做从n个不同元素中取出m个元素的一个排列;从n个不同元素中取出m(m≤n)个元素的所有排列的个数,叫做从n个不同元素中取出m个元素的排列数,用符号 A(n,m)表示。
计算公式: 这里写图片描述
此外规定0!==1

其满足:A(n,m)=n×A(n-1,m-1);(编程时可用此递推)

组合:

定义:从n个不同元素中,任取m(m≤n)个元素并成一组,叫做从n个不同元素中取出m个元素的一个组合;从n个不同元素中取出m(m≤n)个元素的所有组合的个数,叫做从n个不同元素中取出m个元素的组合数。用符号 C(n,m) 表示。
计算公式: 这里写图片描述
两个性质:
1.C(n,m)=C(n,n-m)
2.C(n,m)=C(n-1,m)+C(n-1,m-1);(编程时可用此递推)

对于性质1,
当m>n/2时,就可转换成后一个式子,使运算简化。[当C(n,x)==C(n,y)时,要么x==y要么x+y==n]

对于性质2,
可这样理解:c(n,m)即为从n件物品中选m件的方案数。如果第m件物品不选,方案数就变为c(n-1,m),如果选第m件物品,方案数就变为c(n-1,m-1),总方案数就为两种情况的方案数之和。

最后对于排列组合,补充一个几何问题:
某区有7条南北向街道,5条东西向街道(如图)
这里写图片描述

⑴图中共有多少个矩形?
⑵从A点到B点最近的走法有多少种?

分析:⑴在7条竖线中任选2条,5条横线中任选2条,这样4条线
可组成1个矩形,故可组成矩形C(7,2)·C(5,2)=210个
⑵每条东西向的街道被分成6段,每条南北向的街道被分成4段,从A到B最短的走法,无论怎样走,一定包括10步,其中6步方向相同,另外4步方向相同,每种走法,即是从10步中选出6步,这6步是走东西方向的,共有C(10,6)=210种走法(同样可以从10段中选出4段走南北方向,每一种选法即是1种走法)。所以共有210种走法。(鉴于某个xbq(dalao),我不得不将“段”字改为“步”字,它说这样生动…)

来到题:
bzoj3505
题意:
给定一个nxm的网格,请计算三点都在格点上的三角形共有多少个。注意三角形的三点不能共线。
输入一行,包含两个空格分隔的正整数m和n。
输出一个正整数,为所求三角形数量。

题解:
首先答案就是所有取出三个点的方案数减去会三点共线的方案数。
显然n*m的网格上有(n+1)(m+1)个整点,然后令t=(n+1)(m+1),那么取三个点的方案数就是C(t,3)。
接下来考虑怎么算三点共线的方案数:

有一个结论是在(a,b) (x,y)两点构成的线段上有gcd(a-x,b-y)-1个整点(a>x,b>y)

我们固定(a,b) (x,y)为共线的三点中左边两个,那么第三个点的方案数就是gcd(a-x,b-y)-1
但是这样枚举abxy的复杂度是O(n^2*m^2)
优化是把这线段平移到原点处,平移,平移,平移啊,那么会发现其实只要枚举(0,0) (x-a+1,y-b+1),其他的线段平移就可以了。
画完很容易发现这样(0,0) (x-a+1,y-b+1)的线段可以平移出(n-i+1)*(m-j+1)种不同方案

so 代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
inline int read()
{
    int x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x;
}
inline int gcd(int a,int b){return b==0?a:gcd(b,a%b);}
int n,m;
ll c[1005005][4];
ll ans,tmp;
void getc()
{
     c[0][0]=1;
     for(int i=1;i<=n*m;i++)
     {
         c[i][0]=1;
         for(int j=1;j<=3;j++)
             c[i][j]=c[i-1][j-1]+c[i-1][j];
         }
}
void solve()
{
     ans=c[n*m][3];
     ans-=n*c[m][3];
     ans-=m*c[n][3];
     for(int i=1;i<n;i++)
         for(int j=1;j<m;j++)
         {
             tmp=gcd(i,j)+1;
             if(tmp>2)
                 ans-=(tmp-2)*2*(n-i)*(m-j);
         }
}
int main()
{
    n=read();m=read();
    n++;m++;
    getc();
    solve();
    printf("%lld",ans);
    return 0;
}
  • 3
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值