以下所涉及到的定理不太理解的盆友可以参考《算法笔记》这本书组合数那一章节的内容,故此处不做过多解释
以牛客网小白月赛5为例:
链接:https://www.nowcoder.com/acm/contest/135/F
来源:牛客网
题目描述
签到题来了,送你们一个Python秒的题。
Apojacsleam来到了OI大陆,经过了连年征战,成为了一方国王。
Apojacsleam把他的王国命名为“Apo国”,Apo国的领土是一个标准的圆形。
Apojacsleam现在想封赏他的大臣,他在国境上建立了n个城市,要求他的大臣对这n个城市两两之间修建道路(道路是笔直的),把整个王国分成尽量多的区域,使得每一个大臣都有封土并且不会太大(以免谋反)。
于是Apojacsleam找你求助,他告诉你他打算建多少个城市,而你的任务是告诉他最多可以分成多少个部分。
说的太慢可是要被处死的,所以你必须要在1s之内回答。
输入描述:
输入数据有多组,每组一行,一个正整数n,意义如“题目描述”
输出描述:
对于每一组数据输出一行描述答案:
输出一个正整数k,表示最多分成k份。
示例1
输入
复制
2
3
输出
复制
2
4
样例解释(样例1和样例2一起解释了):
示例2
输入
复制
4
5
6
输出
复制
8
16
31
说明
- 欧拉公式:V-E+F=2; V:vertex顶点。E:edge边 。F:face 面
- 两点连成一条直线,对于圆域,有边数+n(n为圆上分割的线)
- 四个点,两条线最多可以相交一点,共有V=+n
- 对于相交的两条线,形成4条线,除了原有的两条线外,又增加了两条线,所以E=2++n
- 则F=2+E-V=++2
去掉圆外边的部分,则F=2+E-V=++1
对于该题来讲,数据范围很小,开long long 直接列公式即可
ac代码:
#include <bits/stdc++.h>
#define ll unsigned long long int
using namespace std;
int main()
{
ll n;
while(scanf("%lld",&n)!=EOF)
{
ll ans=0;
ans=n*(n-1)*(n-2)*(n-3)/24+n*(n-1)/2+1;
printf("%lld\n",ans);
}
return 0;
}
牛客网暑期多校第八场G题:
链接:https://www.nowcoder.com/acm/contest/146/G
来源:牛客网
题目描述
Niuniu likes mathematics. He also likes drawing pictures. One day, he was trying to draw a regular polygon with n vertices. He connected every pair of the vertices by a straight line as well. He counted the number of regions inside the polygon after he completed his picture. He was wondering how to calculate the number of regions without the picture. Can you calculate the number of regions modulo 1000000007? It is guaranteed that n is odd.
输入描述:
The only line contains one odd number n(3 ≤ n ≤ 1000000000), which is the number of vertices.
输出描述:
Print a single line with one number, which is the answer modulo 1000000007.
示例1
输入
复制
3
输出
复制
1
示例2
输入
复制
5
输出
复制
11
备注:
The following picture shows the picture which is drawn by Niuniu when n=5. Note that no three diagonals share a point when n is odd.
可知F=2+E-V=++1-n,所有数都要开long long
- 费马小定理:
对于b/a%p,如果p为素数且a与p互质,那么a的(p-1)次方除以p的余数恒为1,
a和a^(p-2)互为乘法逆元,则有 b/a%p=b*a^p-2%p
再用迭代法快速幂求a^p-2
对于组合数 %p = %p = %p = %p
所以最简单的做法为直接列式子,用费马小定理求逆元即可
ac代码:
#include<iostream>
using namespace std;
long long mod=1e9+7;
long long pow(long long a,long long b)//求a^b
{
long long ans=1,ret=a;
while(b)
{
if(b&1) ans=ans*ret%mod;
ret=ret*ret%mod;
b>>=1;
}
return ans;
}
int main()
{
long long n;
scanf("%lld",&n);//c(n,4)+c(n,2)+1-n
long long sum1=0,sum2=0,sum=0;
sum1=n*(n-1)%mod*(n-2)%mod*(n-3)%mod*pow(24,mod-2)%mod;
sum2=n*(n-1)%mod*pow(2,mod-2)%mod;//其实pow(2,mod-2)也可以写成(mod+1)/2,因为((mod+1)/2*2)%mod=1;但前提是mod是质数
sum=(sum1+sum2+1-n+mod)%mod;//注意1-n是负数,所以要写成减法取模的形式,否则只过85%
printf("%lld\n",sum);
return 0;
}
或者把后面两项合并一下
#include<iostream>
using namespace std;
long long mod=1e9+7;
long long pow(long long a,long long b)//求a^b
{
long long ans=1,ret=a;
while(b)
{
if(b&1) ans=ans*ret%mod;
ret=ret*ret%mod;
b>>=1;
}
return ans;
}
int main()
{
long long n;
scanf("%lld",&n);//c(n,4)+c(n,2)+1-n
long long sum=0;
sum=n*(n-1)%mod*(n-2)%mod*(n-3)%mod*pow(24,mod-2)%mod;
sum=(sum+(n-1)*(n-2)%mod*pow(2,mod-2)%mod)%mod;
printf("%lld\n",sum);
return 0;
}
- 当m和n的数据范围达到1e+18且mod为素数时,要用卢卡斯定理,同时内层嵌套费马小定理(此处也可以不用卢卡斯定理,因为m和n的数据范围没有那么大,但先暂且保留着吧)
ac代码:(此题也可以不用此方法做,用简单方法即可,但为了以后碰到其他相似求组合数取模的问题可以快速作答,此处依旧放上代码)
#include <iostream>
#include <cmath>
#define ll unsigned long long int
const ll mod=1000000007;
using namespace std;
ll pow(ll a,ll b )//用迭代法求快速幂a^b%mod,即逆元
{
ll ans=1,ret=a;
while(b)
{
if(b&1) ans=ans*ret%mod;
ret=ret*ret%mod;
b>>=1;
}
return ans;
}
ll c(ll n,ll m)//用费马小定理求c(n,m)
{
ll fac=1;
for(ll i=1;i<=m;i++)
{
fac=fac*(n-m+i)%mod;
fac=fac*pow(i,mod-2)%mod;//每一次都要求快速幂,这样最后就是m!的mod-2次方了,也可先求之前列出的费马小定理的公式的左半部分的阶乘,再求右半部分阶乘的快速幂
}
//cout<<fac<<endl;
return fac;
}
ll lucas(ll n,ll m)
{
if(m==0) return 1;
return c(n%mod,m%mod)*lucas(n/mod,m/mod)%mod;
}
int main()
{
ll n,sum1=0,sum2=0,sum=0;
scanf("%lld",&n);
sum1=lucas(n,4);
sum2=lucas(n,2);
//cout<<sum1<<" "<<sum2<<endl;
sum=(sum1+sum2+1-n+mod)%mod;//减法取模
printf("%lld\n",sum);
return 0;
}
- 或者不用费马小定理求逆元,用扩展欧几里得算法求逆元(个人认为代码不是很好记,所以还是采用上面的方法吧)
ac代码:
#include<iostream>
using namespace std;
long long mod=1e9+7;
long long extend_gcd(long long a, long long b, long long &x, long long &y) {
if (b == 0) {
x = 1, y = 0;
return a;
}
else {
long long r = extend_gcd(b, a % b, x, y);
long long temp=x;
x=y;
y=temp-a/b*y;
return r;
}
}
long long inv(long long a, long long n) {
long long x, y;
extend_gcd(a, n, x, y);
x = (x % n + n) % n;
return x;
}
long long C(long long n,long long m)
{
long long ans=1;
for(long long i=1;i<=m;i++)
{
ans=ans*(n-m+i)%mod;
ans=ans*inv(i,mod)%mod;
}
return ans;
}
long long lucas(long long n,long long m)
{
if(m==0) return 1;
return C(n%mod,m%mod)*lucas(n/mod,m/mod)%mod;
}
int main()
{
long long n;
scanf("%lld",&n);
if(n>3)
printf("%lld\n",(lucas(n,2)+lucas(n,4)-n+1+mod)%mod);
else
printf("1\n");
return 0;
}
因为这道题对于来说,m,n的数据范围没有达到1e+18,所以,可以省略卢卡斯定理
ac代码:
#include<iostream>
using namespace std;
long long mod=1e9+7;
long long extend_gcd(long long a, long long b, long long &x, long long &y) {
if (b == 0) {
x = 1, y = 0;
return a;
}
else {
long long r = extend_gcd(b, a % b, x, y);
long long temp=x;
x=y;
y=temp-a/b*y;
return r;
}
}
long long inv(long long a, long long n) {
long long x, y;
extend_gcd(a, n, x, y);
x = (x % n + n) % n;
return x;
}
long long C(long long n,long long m)
{
long long ans=1;
for(long long i=1;i<=m;i++)
{
ans=ans*(n-m+i)%mod;
ans=ans*inv(i,mod)%mod;
}
return ans;
}
int main()
{
long long n;
scanf("%lld",&n);
if(n>3)
printf("%lld\n",(C(n,2)+C(n,4)-n+1+mod)%mod);
else
printf("1\n");
return 0;
}
小白飘过~
欢迎讨论,非诚勿扰哦~邮箱:1308989543@qq.com