数论三之组合数学Ⅰ-Max-Min Sums,Binomial Coefficient is Fun,Strivore,Bubble Sort,放棋子,LOJ6671,Iroha and a Grid

Max-Min Sums

description

solution

加法对于 max ⁡ / min ⁡ \max/\min max/min有分配率,所以单独考虑每个 i i i做最大值/最小值的贡献,加起来即可

a a a排序后,前面选 k − 1 k-1 k1个自己做最大值,后面选 k − 1 k-1 k1个自己做最小值

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 BiAi ∑ i B i ≤ m \sum_{i}B_i\le m iBim,看成 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. 要插ooo中,插左/插右/插中间,最后结果都是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=0n26i×25niCni+s1ni

∣ s ∣ − 1 |s|-1 s1就是减去字符串最后一位位置,因为右边个数确定,伴随着最后一个位置确定

n − i n-i ni个位置还可以随便选,剩下的位置就必须按照 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)nx
  • 每一轮的冒泡排序,若 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 xnxknkx,满足此条件的 x x x可以随便放

n n n个位置中放置 n − k n-k nk个数后,剩下数的放置方案数 k ! k! k!

对于前 n − k n-k nk个放置的数

f ( 1 ) ≤ k ⇒ 1 f(1)\le k\Rightarrow 1 f(1)k1 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(k1)=k!((k+1)nkknk)

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=1nxip的形式很难不联想到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,xi0 yi=xi+1i=1kyipi=1kxipk

对于 ∑ i = 1 k x i = p \sum_{i=1}^kx_i=p i=1kxi=p的方案数利用 n − 1 n-1 n1个档板的挡板法求解 C p + n − 1 n − 1 C_{p+n-1}^{n-1} Cp+n1n1

枚举 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=0nCnk2ki=0pCik+k1k1k=0nCnk2ki=0p1Cik1

∑ 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+1ans=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 i1+j1步,从中选 i − 1 i-1 i1步往下走, C i + j − 2 i − 1 C_{i+j-2}^{i-1} Ci+j2i1

对于本题,有一部分是不能走的,解决方案有两种(本质一样,出发角度不同)

  • 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值