组合计数我最爱
Max-Min Sums
description
solution
加法对于 max / min \max/\min max/min有分配率,所以单独考虑每个 i i i做最大值/最小值的贡献,加起来即可
把 a a a排序后,前面选 k − 1 k-1 k−1个自己做最大值,后面选 k − 1 k-1 k−1个自己做最小值
code
#include <cstdio>
#include <algorithm>
using namespace std;
#define mod 1000000007
#define int long long
#define maxn 100005
int n, k;
int A[maxn], sum[maxn], fac[maxn], inv[maxn];
int qkpow( int x, int y ) {
int ans = 1;
while( y ) {
if( y & 1 ) ans = ans * x % mod;
x = x * x % mod;
y >>= 1;
}
return ans;
}
int C( int n, int m ) {
if( n < m ) return 0;
return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
signed main() {
scanf( "%lld %lld", &n, &k );
inv[0] = fac[0] = 1;
for( int i = 1;i <= n;i ++ )
fac[i] = fac[i - 1] * i % mod;
inv[n] = qkpow( fac[n], mod - 2 );
for( int i = n - 1;i;i -- )
inv[i] = inv[i + 1] * ( i + 1 ) % mod;
for( int i = 1;i <= n;i ++ )
scanf( "%lld", &A[i] );
sort( A + 1, A + n + 1 );
int ans = 0;
for( int i = 1;i <= n;i ++ )
ans = ( ans + C( i - 1, k - 1 ) * A[i] - C( n - i, k - 1 ) * A[i] ) % mod;
printf( "%lld\n", ( ans + mod ) % mod );
return 0;
}
Binomial Coefficient is Fun
description
solution
要乘积产生贡献,必须满足 B i ≥ A i B_i\ge A_i Bi≥Ai, ∑ i B i ≤ m \sum_{i}B_i\le m ∑iBi≤m,看成 m m m个空位
把至少要求的 A i A_i Ai看成第 i i i根小棒,不同 i i i之间加一条分割线分开, A n A_n An的分割线则划分了 ∑ i B i \sum_iB_i ∑iBi的范围
总共是 n + m n+m n+m个空位(新增了 n n n条分割线空位),从中放 ∑ i A i + n \sum_iA_i+n ∑iAi+n根小棒和分割线
小棒和分割线是没有区别的,用组合数求
前 A 1 A_1 A1根小棒划分范围表示 B 1 B_1 B1,以第 A 1 + 1 A_1+1 A1+1根小棒(分割线)划分不同的 B B B
第 A 1 + 2 A_1+2 A1+2到 A 1 + B 1 + 1 A_1+B_1+1 A1+B1+1根小棒划分范围表示 B 2 B_2 B2,以此类推
就与问题的所求式子方案对应
code
#include <cstdio>
#define int long long
#define mod 1000000007
int n, m, sum;
int qkpow( int x, int y ) {
int ans = 1;
while( y ) {
if( y & 1 ) ans = ans * x % mod;
x = x * x % mod;
y >>= 1;
}
return ans;
}
signed main() {
scanf( "%lld %lld", &n, &m );
for( int i = 1, x;i <= n;i ++ )
scanf( "%lld", &x ), sum += x;
int ans = 1;
for( int i = m - sum + 1;i <= n + m;i ++ )
ans = ans * i % mod;
for( int i = 1;i <= sum + n;i ++ )
ans = ans * qkpow( i, mod - 2 ) % mod;
printf( "%lld\n", ans );
return 0;
}
Strivore
description
solution
转化为求 n + ∣ s ∣ n+|s| n+∣s∣的字符串,使得 s s s是其一个(可以不相邻的)子序列
非常恶心的就是如果插入的字符与相邻字符相同,那么插左边插右边本质是没有区别的
只有个数的改变,但是如果单纯用组合数来算,显然会算成多种方案
e.g.
要插o
在oo
中,插左/插右/插中间,最后结果都是ooo
,理应算成一种
所以强制的分类,左右边,在右边的数就可以随便选,在左边的数,就强制与即将相邻字符不同
枚举字符串 s s s最后一位的位置(相当于枚举在右边的数的个数)
在其右边的随便选,左边的去除掉禁止的字符
∑ i = 0 n 2 6 i × 2 5 n − i C n − i + ∣ s ∣ − 1 n − i \sum_{i=0}^n26^i\times 25^{n-i}C_{n-i+|s|-1}^{n-i} i=0∑n26i×25n−iCn−i+∣s∣−1n−i
∣ s ∣ − 1 |s|-1 ∣s∣−1就是减去字符串最后一位位置,因为右边个数确定,伴随着最后一个位置确定
有 n − i n-i n−i个位置还可以随便选,剩下的位置就必须按照 s s s的长相依次填充
code
#include <cstdio>
#include <cstring>
#define maxn 2000005
#define int long long
#define mod 1000000007
int fac[maxn], inv[maxn];
char s[maxn];
int n, m;
int qkpow( int x, int y ) {
int ans = 1;
while( y ) {
if( y & 1 ) ans = ans * x % mod;
x = x * x % mod;
y >>= 1;
}
return ans;
}
void init( int n ) {
fac[0] = inv[0] = 1;
for( int i = 1;i <= n;i ++ )
fac[i] = fac[i - 1] * i % mod;
inv[n] = qkpow( fac[n], mod - 2 );
for( int i = n - 1;i;i -- )
inv[i] = inv[i + 1] * ( i + 1 ) % mod;
}
int C( int n, int m ) {
return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
signed main() {
scanf( "%lld %s", &n, s + 1 );
m = strlen( s + 1 );
init( n + m );
int ans = 0;
for( int i = 0;i <= n;i ++ )
ans = ( ans + qkpow( 26, i ) * qkpow( 25, n - i ) % mod * C( n - i + m - 1, n - i ) % mod ) % mod;
printf( "%lld\n", ans );
return 0;
}
Bubble Sort
description
solution
定义函数 f ( x ) : x f(x):x f(x):x元素左边且比 x x x大的元素个数
- ∀ x f ( x ) = 0 \forall_xf(x)=0 ∀xf(x)=0表示有序状态
- f ( x ) ≤ n − x f(x)\le n-x f(x)≤n−x
- 每一轮的冒泡排序,若 f ( x ) ≠ 0 f(x)≠0 f(x)=0,则 f ( x ) − − f(x)-- f(x)−−
显然 max { f ( x ) } = k \max\{f(x)\}=k max{f(x)}=k才恰好是 k k k轮的冒泡排序
比起求恰好 k k k轮的冒泡排序,不超过 k k k轮的冒泡排序 g ( k ) g(k) g(k)更好求
∀ x n − x ≤ k ⇒ n − k ≤ x \forall_xn-x\le k\Rightarrow n-k\le x ∀xn−x≤k⇒n−k≤x,满足此条件的 x x x可以随便放
在 n n n个位置中放置 n − k n-k n−k个数后,剩下数的放置方案数 k ! k! k!
对于前 n − k n-k n−k个放置的数
f ( 1 ) ≤ k ⇒ 1 f(1)\le k\Rightarrow 1 f(1)≤k⇒1有 k + 1 k+1 k+1个位置可以放(前 k + 1 k+1 k+1个), 1 1 1对 f ( 2 ) f(2) f(2)不会有影响,所以 2 2 2同样有 k + 1 k+1 k+1个位置可放 . . . ... ...
最终结果为 g ( k ) − g ( k − 1 ) = k ! ( ( k + 1 ) n − k − k n − k ) g(k)-g(k-1)=k!\Big((k+1)^{n-k}-k^{n-k}\Big) g(k)−g(k−1)=k!((k+1)n−k−kn−k)
code
#include <cstdio>
#define int long long
#define mod 20100713
#define maxn 1000005
int fac[maxn];
int qkpow( int x, int y ) {
int ans = 1;
while( y ) {
if( y & 1 ) ans = ans * x % mod;
x = x * x % mod;
y >>= 1;
}
return ans;
}
signed main() {
int T, n, k;
scanf( "%lld", &T );
fac[0] = 1;
for( int i = 1;i <= 1e6;i ++ )
fac[i] = fac[i - 1] * i % mod;
while( T -- ) {
scanf( "%lld %lld", &n, &k );
printf( "%lld\n", ( qkpow( k + 1, n - k ) - qkpow( k, n - k ) + mod ) % mod * fac[k] % mod );
}
return 0;
}
[HAOI2016]放棋子
description
solution
每行每列都只有一个障碍,除去这些障碍,保证每行每列恰好只放了一个棋子的方案数
本质其实就是错排数,障碍就相当于 i i i自身(在方案中不能与 i i i下标对应)
知道这个后就剩下大整数操作了
没有模数真是难,有了模数还是难
code
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
struct Int {
int g[5000], len;
Int() {
memset( g, 0, sizeof( g ) );
len = 0;
}
Int( int x ) {
memset( g, 0, sizeof( g ) );
len = 0;
if( ! x ) len = 1;
else while( x )
g[len ++] = x % 10, x /= 10;
}
void clean() {
while( len > 1 && ! g[len - 1] ) len --;
}
Int operator + ( Int t ) {
static Int ans;
ans.len = 0; int r = 0;
for( int i = 0;i < max( len, t.len );i ++ ) {
int x = r;
if( i < len ) x += g[i];
if( i < t.len ) x += t.g[i];
ans.g[ans.len ++] = x % 10;
r = x / 10;
}
ans.g[ans.len ++] = r;
ans.clean();
return ans;
}
Int operator * ( Int t ) {
Int ans;
ans.len = t.len + len;
for( int i = 0;i < len;i ++ )
for( int j = 0;j < t.len;j ++ )
ans.g[i + j] += g[i] * t.g[j];
for( int i = 0;i < ans.len;i ++ )
ans.g[i + 1] += ans.g[i] / 10, ans.g[i] %= 10;
ans.len ++;
ans.clean();
return ans;
}
void print() {
for( int i = len - 1;~ i;i -- )
printf( "%d", g[i] );
}
}D[205];
int main() {
int n;
scanf( "%d", &n );
D[1] = 0, D[2] = 1;
for( int i = 3;i <= n;i ++ )
D[i] = ( D[i - 1] + D[i - 2] ) * ( i - 1 );
D[n].print();
return 0;
}
EntropyIncreaser 与 Minecraft
description
solution
∑ i = 1 n x i ≤ p \sum_{i=1}^nx_i\le p ∑i=1nxi≤p的形式很难不联想到Binomial Coefficient is Fun一题的形式
枚举有多少个 x i = 0 x_i=0 xi=0,剩下 k k k个 ∣ x i ∣ > 0 |x_i|>0 ∣xi∣>0,那么方案数首先有 2 k 2^k 2k
设 ∀ i , x i ≥ 0 y i = x i + 1 ⇒ ∑ i = 1 k y i ≤ p ⇒ ∑ i = 1 k x i ≤ p − k \forall_{i,x_i\ge 0}\ y_i=x_i+1\Rightarrow \sum_{i=1}^ky_i\le p\Rightarrow \sum_{i=1}^kx_i\le p-k ∀i,xi≥0 yi=xi+1⇒∑i=1kyi≤p⇒∑i=1kxi≤p−k
对于 ∑ i = 1 k x i = p \sum_{i=1}^kx_i=p ∑i=1kxi=p的方案数利用 n − 1 n-1 n−1个档板的挡板法求解 C p + n − 1 n − 1 C_{p+n-1}^{n-1} Cp+n−1n−1
枚举 p p p,答案为 ∑ k = 0 n C n k 2 k ∑ i = 0 p C i − k + k − 1 k − 1 ⇔ ∑ k = 0 n C n k 2 k ∑ i = 0 p − 1 C i k − 1 \sum_{k=0}^nC_n^k2^k\sum_{i=0}^pC_{i-k+k-1}^{k-1}\Leftrightarrow \sum_{k=0}^nC_n^k2^k\sum_{i=0}^{p-1}C_{i}^{k-1} ∑k=0nCnk2k∑i=0pCi−k+k−1k−1⇔∑k=0nCnk2k∑i=0p−1Cik−1
∑ i = 0 n C i m = C n + 1 m + 1 ⇒ a n s = ∑ k = 0 n C n k 2 k C p k \sum_{i=0}^nC_i^m=C_{n+1}^{m+1}\Rightarrow ans=\sum_{k=0}^nC_n^k2^kC_{p}^{k} ∑i=0nCim=Cn+1m+1⇒ans=∑k=0nCnk2kCpk
code
#include <cstdio>
#define mod 1000000007
#define int long long
#define maxn 1000005
int n, p;
int fac[maxn], inv[maxn];
int qkpow( int x, int y ) {
int ans = 1;
while( y ) {
if( y & 1 ) ans = ans * x % mod;
x = x * x % mod;
y >>= 1;
}
return ans;
}
void init() {
fac[0] = inv[0] = 1;
for( int i = 1;i <= n;i ++ )
fac[i] = fac[i - 1] * i % mod;
inv[n] = qkpow( fac[n], mod - 2 );
for( int i = n - 1;i;i -- )
inv[i] = inv[i + 1] * ( i + 1 ) % mod;
}
int C( int n, int m ) {
if( n < m ) return 0;
return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
signed main() {
scanf( "%lld %lld", &n, &p );
init();
int ans = 0, mi = 1, MS = 1;
for( int i = 0;i <= n;i ++, mi = ( mi << 1 ) % mod ) {
ans = ( ans + C( n, i ) * mi % mod * MS % mod ) % mod;
MS = MS * ( p - i ) % mod * qkpow( i + 1, mod - 2 ) % mod;
}
printf( "%lld\n", ans );
return 0;
}
D - Iroha and a Grid
description
solution
从起点到某个点 ( i , j ) (i,j) (i,j)的方案数:一共走 i − 1 + j − 1 i-1+j-1 i−1+j−1步,从中选 i − 1 i-1 i−1步往下走, C i + j − 2 i − 1 C_{i+j-2}^{i-1} Ci+j−2i−1
对于本题,有一部分是不能走的,解决方案有两种(本质一样,出发角度不同)
-
solution1
总方案➖从起点到被禁止区间上一行的,再强制向下走一步,最后被禁止格子到终点的方案
必须强制向下走一步,如若不然
则第一个红格子不合法的方案和第二个红格子的不合法方案都会包含既经过红格子1又经过红格子2的方案
-
solution2
直接计算合法方案数,同样到被禁止上一行的时候,强制向下走一步
code
#include <cstdio>
#define maxn 200005
#define int long long
#define mod 1000000007
int n, m, A, B;
int fac[maxn], inv[maxn];
int qkpow( int x, int y ) {
int ans = 1;
while( y ) {
if( y & 1 ) ans = ans * x % mod;
x = x * x % mod;
y >>= 1;
}
return ans;
}
void init() {
fac[0] = inv[0] = 1;
for( int i = 1;i < maxn;i ++ )
fac[i] = fac[i - 1] * i % mod;
inv[maxn - 1] = qkpow( fac[maxn - 1], mod - 2 );
for( int i = maxn - 2;i;i -- )
inv[i] = inv[i + 1] * ( i + 1 ) % mod;
}
int C( int n, int m ) {
if( n < m ) return 0;
return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
signed main() {
init();
scanf( "%lld %lld %lld %lld", &n, &m, &A, &B );
int ans = 0;
for( int i = B + 1;i <= m;i ++ )
ans = ( ans + C( n - A + i - 2, i - 1 ) * C( m - i + A - 1, m - i ) % mod ) % mod;
printf( "%lld\n", ans );
return 0;
}