抽屉原理
经典应用:
给出一个含有 n 个数字的序列,找一个连续的子序列,使他们的和是 c 的倍数
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
ll a[N],dr[N];
ll sum[N];
int main(){
ll c,n;
while(~scanf("%lld%lld",&c,&n)){
memset(dr,-1,sizeof(dr));
dr[0] = 0;
sum[0] = 0;
for(ll i = 1;i <= n;i ++){
scanf("%lld",&a[i]);
sum[i] = sum[i - 1] + a[i];
}
for(ll i = 1;i <= n;i ++){
if(dr[sum[i] % c] != -1){
for(ll j = dr[sum[i] % c] + 1;j < i;j ++) printf("%lld",j);
printf("%lld\n",i);
break;
}
dr[sum[i] % c] = i;
}
}
return 0;
}
容斥原理
记住核心:奇加偶减
应用:
1.求[a,b]中与n互质的数的数量
容斥思想体现在cal函数中
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 110;
typedef long long ll;
bool bprime[N];
ll prime[N],cnt,fac[N],num;
void isp(){//筛素数
cnt = 0;
memset(bprime,0,sizeof(bprime));
for(int i = 2;i < N;i ++){
if(!bprime[i]){
prime[cnt ++] = i;
for(int j = i * 2;j < N;j += i)
bprime[i] = true;
}
}
}
void getfac(int n){//质因数分解
num = 0;
for(int i = 0;prime[i] * prime[i] <= n && i < cnt;i ++){
if(n % prime[i] == 0){
fac[num ++] = prime[i];
while(n % prime[i] == 0)
n /= prime[i];
}
}
if(n != 1) fac[num ++] = n;
}
ll cal(int m,int num){
ll ans = 0;
for(ll i = 1;i < (1 << num);i ++){
ll sum = 0;
ll tmp = 1;
for(ll j = 0;j < num;j ++){
if(i & (1 << j)){
sum ++;
tmp *= fac[j];
}
}
if(sum % 2) ans += m / tmp;
else ans -= m / tmp;
}
}
int main(){
isp();
ll a,b,n;
scanf("%lld%lld%lld",&a,&b,&n);
getfac(n);
ll res = (b - (a - 1) - cal(b,num) + cal(a - 1,num));
printf("%lld\n",res);
return 0;
}
2.求[1,n]中被m整除的数的个数
这个也类似
LL GCD(LL a,LL b){
return !b?a:GCD(b,a%b);
}
LL LCM(LL a,LL b){
return a/GCD(a,b)*b;
}
LL a[N];
int main(){
LL n;
int m;
scanf("%lld%d",&n,&m);
for(int i=0;i<m;i++)
scanf("%lld",&a[i]);
LL sum=0;
for(int i=0;i<(1<<m);i++){//2^m种状态
LL lcm=1;
LL cnt=0;
for(int j=0;j<m;j++){
if(i&(1<<j)){//从m中选出j个数
lcm=LCM(lcm,a[j]);
cnt++;
}
}
if(cnt!=0){
if(cnt&1)//奇加
sum+=n/lcm;
else//偶减
sum-=n/lcm;
}
}
printf(%lld "%lld\n",sum,n-sum);
return 0;
}
组合数计算
杨辉三角打表
int f[N][N];
void init(){
for(int i = 1;i <= N;i ++){
f[i][0] = 1;
f[i][i] = 1;
for(int j = 1;j <= N;j ++){
f[i][j] = f[i - 1][j] + f[i - 1][j - 1];
}
}
}
约分求重数
组合数取模
用逆元(要求p为质数)
#include<cstdio>
using namespace std;
const int mod = 1e9 + 7;
const int N = 1e5 + 10;
typedef long long ll;
ll fac[N];
void init(){//预处理出阶乘
fac[1] = 1;
for(int i = 1;i <= N;i ++)
fac[i] = fac[i - 1] * i % mod;
}
ll qpow(int a,int b){//快速幂求逆元
ll ans = 1;
while(b){
if(b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
int main(){
int n,m;
init();
scanf("%d",&n,&m);
ll ans = fac[n] * qpow(fac[m],mod - 2) % mod * qpow(fac[n - m],mod - 2) % mod;
printf("lld\n",ans);
return 0;
}
卢卡斯定理
递归实现:
#include<cstdio>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
const int mod = 1e6 + 7;
ll fac[N];
void getfac(){//预处理阶乘
fac[0] = 1;
for(int i = 1;i < N;i ++){
fac[i] = fac[i - 1] * i % mod;
}
}
ll qpow(ll a,ll b){
ll ans = 1;
while(b){
if(b & 1){
ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
}
return ans;
}
ll getC(ll n,ll m){//获取C(n,m) % mod
if(m > n) return 0;
return fac[n] * (qpow(fac[m] * fac[n - m] % mod,mod - 2)) % mod;
}
ll lucas(ll n,ll m){
if(m == 0) return 1;
return getC(n % mod, m % mod) * lucas(n / mod, m / mod) % mod;
}
int main(){
getfac();
ll n,m;
scanf("%lld%lld",&n,&m);
printf("%lld\n",lucas(n,m));
return 0;
}
卡特兰数列
最经典的问题就是:
有n个0和n个1,问满足前k个数中1的个数大于0的个数的数列种类数
具体可以看这个:
卡特兰解释与应用
n<35的卡特兰数列算法
ll h[36];
void init(){
h[0] = h[1] = 1;
for(int i = 2;i <= 35;i ++){
h[i] = 0;
for(int j = 0;j < i;j ++){
h[i] += h[j] * h[i - j - 1];
}
}
}
n<100的卡特兰数列算法(高精)
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 110;
int a[N][N];
void mul(int num,int n,int b){
int tmp = 0;
for(int i = n - 1;i >= 0;i --){
tmp += b * a[num][i];
a[num][i] = tmp % 10;
tmp /= 10;
}
}
void div(int num,int n,int b){
int tmp = 0;
for(int i = 0;i < n;i ++){
tmp = tmp * 10 + a[num][i];
a[num][i] = tmp / b;
tmp %= b;
}
}
void init(){
memset(a,0,sizeof(a));
a[1][N - 1] = 1;
for(int i = 2;i <= N;i ++){
memcpy(a[i],a[i - 1],sizeof(a[i - 1]));
mul(i,N,4 * i - 2);
div(i,N,i + 1);
}
}
int main(){
init();
int n;
while(scanf("%d",&n) != EOF){
int i;
for(i = 0;i < N && !a[n][i];i ++);
printf("%d",a[n][i ++]);
for(;i < N;i ++)
printf("%d",a[n][i]);
printf("\n");
}
return 0;
}