洛谷 U96762 小R与三角形 题解

U96762 小R与三角形

原题链接

题目描述

小 R 所在的小镇有 n 个村落,这 n 个村落分布在一个圆周上,这些村落之间两两有直达的小路,小路可能相交,但不存在三条路交于一点。现在小 R 正好放暑假了,每天都在村落间游荡。一天,他发现可以从一个非村落的点出发,在不经过村落的情况下经过三条小路再回到这个点。于是他很好奇,一共有多少个这样的回路呢?

输入格式

第一行一个正整数 n,含义如上。

输出格式

第一行一个正整数,表示回路的个数

输入输出样例

输入 #1

6

输出 #1

1

输入 #2

20

输出 #2

38760

说明/提示

对于 50%的数据,有 n<=20。 对于 100%的数据,有 n<=100。

【思路】

先分析一下提议,求在圆上n个点互相连接之后构成的顶点不是这n个点中任意一个点的三角形有多少个。
看起来很麻烦的样子对不对?一想,哇,这么多边花里胡哨的怎么搞好啊?
其实很简单,样例已经看穿了一切(手动滑稽)
第一个样例6个点可以构成1个这样的三角形,所以可以类比出来任意6个点都可以构成这么一个三角形,所以n个点能够构成多少个这样的三角形,就是在n个里面取出6个的组合数。
怎么样是不是很简单的亚子!!

【暴力求组合数】

组合数的公式是这样的:

\[C_n^m = \dfrac{m!}{n!(n-m)!} \]

直接暴力算就可以了,但是有一个很难受的地方就是这道题没有模数,所以这样暴力求阶乘很容易爆long long
所以只能拿50分

【完整代码】

#include<iostream>
#include<cstdio>
#define int long long

using namespace std;

int read()
{
	int sum = 0,fg = 1;
	char c = getchar();
	while(c < '0' || c > '9')
	{
		if(c == '-')fg = -1;
		c = getchar();
	}
	while(c >= '0' && c <= '9')
	{
		sum = sum * 10 + c - '0';
		c = getchar();
	}
	return sum * fg;
}

int jc(int x)
{
	int ans = 1;
	for(register int i = 1;i <= x;++ i)
		ans *= i;
	return ans;
}

int C(int n,int m)
{
	return jc(n) / (jc(m) * (jc(n - m)));
}

signed main()
{
	int n = read();
	cout << C(n,6) << endl;
}

递归求组合数

阶乘求组合数爆掉的原因是因为超出了long long,因为\(C_n^m\)本身没有超出long long的范围,只是在除出正确的结果之前先爆掉了long long,所以可以用递归求组合数的方法吼!因为这样是直接求\(C_n^m\)的值,所以不会爆掉。

虽然不会爆long long了,但是,很可怕的一件事出现了,那就是超时了,因为这个递归求组合数复杂度太高了,会超时,只能拿70分。

【完整代码】

#include<iostream>
#include<cstdio>

using namespace std;

int read()
{
	int sum = 0,fg = 1;
	char c = getchar();
	while(c < '0' || c > '9'){if(c == '-')fg = -1;c = getchar();}
	while(c >= '0' && c <= '9'){sum = sum * 10 + c - '0';c = getchar();}
	return sum * fg;
}

int C(int n,int m)
{
	if(n == m)return 1;
	if(m == 0)return 1;
	return C(n - 1,m) + C(n - 1,m - 1);
}

int main()
{
	int n = read();
	if(n <= 5)
	{
		cout << 0 << endl;
		return 0; 
	}
	cout << C(n,6) << endl;
}

但是,超时也是有原因的——重复计算多次组合数
这就是100*100,一共才10000中组合数,一个只计算一遍都不会超时,所以出现超时情况只能是重复计算,所以记忆化就用上啦!
轻轻松松A掉,跑的飞快。

【完整代码】

#include<iostream>
#include<cstdio>

using namespace std;
const int Max = 101;
int c[Max][Max];
int read()
{
	int sum = 0,fg = 1;
	char c = getchar();
	while(c < '0' || c > '9'){if(c == '-')fg = -1;c = getchar();}
	while(c >= '0' && c <= '9'){sum = sum * 10 + c - '0';c = getchar();}
	return sum * fg;
}

int C(int n,int m)
{
	if(c[n][m] != 0)return c[n][m];
	if(n == m)return c[n][m] = 1;
	if(m == 0)return c[n][m] = 1;
	return c[n][m] = C(n - 1,m) + C(n - 1,m - 1);
}

int main()
{
	int n = read();
	if(n <= 5)
	{
		cout << 0 << endl;
		return 0; 
	}
	cout << C(n,6) << endl;
}

【阶乘公式改进法】

但是我a某人觉得记忆化不够优美,所以要改进这个阶乘公式!

\[C_n^m = \dfrac{n!}{m!(n-m)!} \]

因为m是一定的,而且阶乘暴力求解只在n大的时候才会爆,为什么要提到这个呢?原因就是n!是1到n乘起来,除以了1到m乘起来的数和1到(n-m)乘起来的数,上面的1到n乘起来的数可以和下面某一个越掉一部分,比如和1到m乘起来的数一约就变为了m+1到n乘起来的数。
但是这两个怎么选择呢?就用到了刚才提到的,在n大的时候才会爆掉,所以和1到(n-m)乘起来的数约掉显然是更优的,毕竟m!阶乘就是个720太小了,不如前者更优。
所以式子就可以化为

\[C_n^m = \dfrac{(n - 5) * (n - 4) * (n - 3) * (n - 2) * (n - 1) * n}{720} \]

轻轻松松!

【完整代码】

#include<iostream>
#include<cstdio>
#define int long long

using namespace std;
const int Max = 101;
int c[Max][Max];
int read()
{
	int sum = 0,fg = 1;
	char c = getchar();
	while(c < '0' || c > '9'){if(c == '-')fg = -1;c = getchar();}
	while(c >= '0' && c <= '9'){sum = sum * 10 + c - '0';c = getchar();}
	return sum * fg;
}

int C(int n,int m)
{
	if(c[n][m] != 0)return c[n][m];
	if(n == m)return c[n][m] = 1;
	if(m == 0)return c[n][m] = 1;
	return c[n][m] = C(n - 1,m) + C(n - 1,m - 1);
}

signed main()
{
	int n = read();
	if(n <= 5)
	{
		cout << 0 << endl;
		return 0; 
	}
	int ans = 1;
	for(register int i = n - 5;i <= n;++ i)
		ans *= i;
	cout << ans / 720 << endl;
	return 0;
}
posted @ 2019-11-13 15:20  acioi  阅读(462)  评论(0编辑  收藏  举报