组合计数我最爱
- Max-Min Sums
- description
- solution
- code
- Binomial Coefficient is Fun
- description
- solution
- code
- Strivore
- description
- solution
- code
- Bubble Sort
- description
- solution
- code
- [HAOI2016]放棋子
- description
- solution
- code
- EntropyIncreaser 与 Minecraft
- description
- solution
- code
- D - Iroha and a Grid
- description
- solution
- code
Max-Min Sums
description
solution
加法对于max/min\max/\minmax/min有分配率,所以单独考虑每个iii做最大值/最小值的贡献,加起来即可
把aaa排序后,前面选k−1k-1k−1个自己做最大值,后面选k−1k-1k−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
要乘积产生贡献,必须满足Bi≥AiB_i\ge A_iBi≥Ai,∑iBi≤m\sum_{i}B_i\le m∑iBi≤m,看成mmm个空位
把至少要求的AiA_iAi看成第iii根小棒,不同iii之间加一条分割线分开,AnA_nAn的分割线则划分了∑iBi\sum_iB_i∑iBi的范围
总共是n+mn+mn+m个空位(新增了nnn条分割线空位),从中放∑iAi+n\sum_iA_i+n∑iAi+n根小棒和分割线
小棒和分割线是没有区别的,用组合数求
前A1A_1A1根小棒划分范围表示B1B_1B1,以第A1+1A_1+1A1+1根小棒(分割线)划分不同的BBB
第A1+2A_1+2A1+2到A1+B1+1A_1+B_1+1A1+B1+1根小棒划分范围表示B2B_2B2,以此类推
就与问题的所求式子方案对应
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∣的字符串,使得sss是其一个(可以不相邻的)子序列
非常恶心的就是如果插入的字符与相邻字符相同,那么插左边插右边本质是没有区别的
只有个数的改变,但是如果单纯用组合数来算,显然会算成多种方案
e.g.
要插o
在oo
中,插左/插右/插中间,最后结果都是ooo
,理应算成一种
所以强制的分类,左右边,在右边的数就可以随便选,在左边的数,就强制与即将相邻字符不同
枚举字符串sss最后一位的位置(相当于枚举在右边的数的个数)
在其右边的随便选,左边的去除掉禁止的字符
∑i=0n26i×25n−iCn−i+∣s∣−1n−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−in-in−i个位置还可以随便选,剩下的位置就必须按照sss的长相依次填充
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):xf(x):xf(x):x元素左边且比xxx大的元素个数
- ∀xf(x)=0\forall_xf(x)=0∀xf(x)=0表示有序状态
- f(x)≤n−xf(x)\le n-xf(x)≤n−x
- 每一轮的冒泡排序,若f(x)≠0f(x)≠0f(x)=0,则f(x)−−f(x)--f(x)−−
显然max{f(x)}=k\max\{f(x)\}=kmax{f(x)}=k才恰好是kkk轮的冒泡排序
比起求恰好kkk轮的冒泡排序,不超过kkk轮的冒泡排序g(k)g(k)g(k)更好求
∀xn−x≤k⇒n−k≤x\forall_xn-x\le k\Rightarrow n-k\le x∀xn−x≤k⇒n−k≤x,满足此条件的xxx可以随便放
在nnn个位置中放置n−kn-kn−k个数后,剩下数的放置方案数k!k!k!
对于前n−kn-kn−k个放置的数
f(1)≤k⇒1f(1)\le k\Rightarrow 1f(1)≤k⇒1有k+1k+1k+1个位置可以放(前k+1k+1k+1个),111对f(2)f(2)f(2)不会有影响,所以222同样有k+1k+1k+1个位置可放.........
最终结果为g(k)−g(k−1)=k!((k+1)n−k−kn−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
每行每列都只有一个障碍,除去这些障碍,保证每行每列恰好只放了一个棋子的方案数
本质其实就是错排数,障碍就相当于iii自身(在方案中不能与iii下标对应)
知道这个后就剩下大整数操作了
没有模数真是难,有了模数还是难
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=1nxi≤p\sum_{i=1}^nx_i\le p∑i=1nxi≤p的形式很难不联想到Binomial Coefficient is Fun一题的形式
枚举有多少个xi=0x_i=0xi=0,剩下kkk个∣xi∣>0|x_i|>0∣xi∣>0,那么方案数首先有2k2^k2k
设∀i,xi≥0yi=xi+1⇒∑i=1kyi≤p⇒∑i=1kxi≤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=1kxi=p\sum_{i=1}^kx_i=p∑i=1kxi=p的方案数利用n−1n-1n−1个档板的挡板法求解Cp+n−1n−1C_{p+n-1}^{n-1}Cp+n−1n−1
枚举ppp,答案为∑k=0nCnk2k∑i=0pCi−k+k−1k−1⇔∑k=0nCnk2k∑i=0p−1Cik−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=0nCim=Cn+1m+1⇒ans=∑k=0nCnk2kCpk\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−1i-1+j-1i−1+j−1步,从中选i−1i-1i−1步往下走,Ci+j−2i−1C_{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;
}