组合数
组合数
基本做法
先看看不做预处理计算一个组合数:
int C(int r, int n)
{
int ans = 1;
for (int i = 1; i <= r; i++)
{
ans *= n - i + 1;
ans /= i;
}
return ans;
}
预处理组合数
针对大多数仅仅是利用组合数求解问题的题目运用递推法打表,不仅方便,而且可以稳稳地控制复杂度,对于需要多次引用组合数的题目效果极佳:
基于组合数公理性质:
推得:
由这个递推公式就可以熟练的写出组合数代码,但要注意初始化:
同时,把表打出来后,我们会发现这就是杨辉三角,这个三角可以解决很多问题,记住打印三角的方法也可以打出组合数。
inline void build()
{
c[0][0]=1;
c[1][0]=c[1][1]=1;//如上初始化,绝对绝对不能忘记或错,结合常识。
for(int i=2;i<=2000;i++)
{
c[i][0]=1;
for(int j=1;j<=2000;j++)
c[i][j]=c[i-1][j-1]+c[i-1][j];//递推公式。
}
}
例题
这题还需要再用前缀和来优化查询速度
前缀和可以有效减少查询统计时的复杂度,每一次查询\(O(n)\)降到\(O(1)\)
记住:上加左 减左上 加自己
\(ans[i][j]=ans[i][j-1]+ans[i-1][j]-ans[i-1][j-1]\)
inline void build()
{
c[0][0]=1;
c[1][0]=c[1][1]=1;
for(int i=2;i<=2000;i++)
{
c[i][0]=1;
for(int j=1;j<=i;j++)
{
c[i][j]=(c[i-1][j-1]+c[i-1][j])%k;
ans[i][j]=ans[i-1][j]+ans[i][j-1]-ans[i-1][j-1];//前缀和。
if(!c[i][j])ans[i][j]++;//如果满足结论,计数加一。
}
ans[i][i+1]=ans[i][i];//继承。
}
}
inline void solve()
{
t=read(),k=read();
build();
while(t--)
{
n=read(),m=read();
if(m>n)printf("%lld\n",ans[n][n]);//如果m>n,ans只会达到n,只需输出ans[n,n]就可以了。
else printf("%lld\n",ans[n][m]);
}
}
- 需掌握组合数的基本两种求解方法(通项公式,递推公式),根据数据范围选定方法。
- 结合数据范围找到优化方法,无论是取模还是自己的玄学优化都尝试一下.(建议取模输出的题,绝对不要乱模!既费时又易错)
- 掌握前缀和,利用前缀和对降维的作用。
拿 cgx
举例,设 \(ans\) 为比 cgx
小的单词个数,初值为 \(0\)。
\(\large1.\) 首先,cgx
肯定比只有一个字母的单词大,\(ans+26,\quad ans=26\)。
\(\large2.\) 其次,cgx
肯定比只有两个字母的单词大。
只有两个字母的单词个数该怎么算呢?就是在 \(26\) 个字母中选 \(2\) 个。
\(ans+C_{26}^{2},C_{26}^{2}=\frac{26*25}{2*1}=325,\quad ans=26+325=351\)。
\(\large3.\) 只有三个字母
\(3.1.\) 第一位:它比以字母 a-b
为第一位的大( cgx
第一位为 c
,所以要比 c
小)
-
以
a
为第一位,且有三位的单词个数:\(C_{25}^{2}=\frac{25*24}{2*1}=300\)。从比
a
大的剩下 \(25\) 个字母中选 \(2\) 个字母:
\(ans+300,\quad ans=351+300=651\)。 -
以
b
为第一位,且有三位的个数:\(C_{24}^{2}=\frac{24*23}{2*1}=276\)。从比 \(b\) 大的剩下 \(24\) 个字母中选 \(2\) 个字母:\(ans+276,\quad ans=651+276=927\)。
\(3.2\) 第二位(即第一位已经确定为 \(c\)):它比以字母 d-f
为第二位的单词大 (第一位为 c
,所以要比 c
大,第二位为 g
,所以要比 g
小)
-
第二位为
d
的个数:\(C_{22}^{1}=\frac{22}{1}=22\)。从比
d
大的剩下 \(22\) 个字母中选 \(1\) 个字母:\(ans+22,\quad ans=927+22=949\)。 -
第二位为
e
的个数:\(C^{1}_{21}=21,ans+21,\quad ans=949+21=970\)。 -
第二位为
f
的个数:\(C^{1}_{20}=20,ans+20,\quad ans=970+20=990\)。
\(3.3\) 第三位(一,二位已经确定):它比以字母 h-w
为第三位的大,共有 \(16\) 个。
\(ans+16,\quad ans=990+16=1006\)
因为比 cgx
小的单词共有 \(1006\) 个,所以 cgx
是第 \(1007\) 个。
先判断该单词是否存在,再一位一位地去考虑共有多少比给出单词小的单词,如果用的是字符串,则还需要考虑一下边界问题。
#include<bits/stdc++.h>
using namespace std;
string s;
int ans,n;
int c(int m,int n)//组合数计算
{
if(m==0)return 1;
int mut=1;
for(int i=n;i>n-m;i--)mut*=i;
for(int i=m;i>1;i--)mut/=i;
return mut;
}
int main()
{
cin>>s,n=s.size();
for(int i=1;i<n;i++)
if(s[i]<=s[i-1])cout<<0,exit(0);//判断是否存在
for(int i=1;i<n;i++)ans+=c(i,26);//计算出位数比它小的单词数
for(int i=0;i<n;i++)//枚举每一位
for(char j=(i==0?'a':s[i-1]+1);j<s[i];j++)//注意考虑边界
ans+=c(n-i-1,'z'-j);//因为字符串下标从0开始,所以是n-i-1而不是n-i
cout<<++ans;//别忘了最后把自己加上
return 0;
}