zoukankan      html  css  js  c++  java
  • 洛谷 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;
    }
    
  • 相关阅读:
    14个你可能不知道的JavaScript调试技巧
    数据库设计四步骤
    mac 卸载 jdk
    node版本管理
    mysql order by limit 问题
    计算机一些基本概念的认识
    SQL设置主外键关联时报错
    阻止表单autocomplete
    常见字符编码
    编程语言分类
  • 原文地址:https://www.cnblogs.com/acioi/p/11849681.html
Copyright © 2011-2022 走看看