数论三之排列组合Ⅱ——Virus Tree 2,RGB Coloring,123 Triangle,排列计数,排队,卡农

Virus Tree 2

description

solution

距离不超过 2 2 2的点,可能是儿子/孙子/父亲/爷爷

考虑从上到下,由上面的颜色选取决定下面的颜色

显然,第一个点有 K K K种选择,第二个点有 K − 1 K-1 K1种,每次不同都要 − 1 -1 1

答案就是每个点的选择的乘积

所以只需要把这种过程的递减通过dfs树来实现

对于现在的子树根节点 u u u,如果 v v v是第一个儿子,那么需要考虑 v v v有没有爷爷

如果不是第一个儿子,那么就是前一个儿子的方案数再 − 1 -1 1

code

#include <cstdio>
#include <vector>
using namespace std;
#define mod 1000000007
#define int long long
#define maxn 1000005
vector < int > G[maxn];
int n, k;
int w[maxn];

void dfs( int u, int fa ) {
	int r = 0;
	for( auto v : G[u] ) {
		if( v == fa ) continue;
		else {
			if( r ) w[v] = r - 1;
			else if( ! fa ) w[v] = k - 1;
			else w[v] = k - 2;
			r = w[v]; 
		}
	}
	for( auto v : G[u] ) 
		if( v ^ fa ) dfs( v, u );
}

signed main() {
	scanf( "%lld %lld", &n, &k );
	for( int i = 1, u, v;i < n;i ++ ) {
		scanf( "%lld %lld", &u, &v );
		G[u].push_back( v );
		G[v].push_back( u );
	}
	w[1] = k;
	dfs( 1, 0 );
	int ans = 1;
	for( int i = 1;i <= n;i ++ )
		if( w[i] <= 0 ) return ! printf( "0\n" );
		else ans = ans * w[i] % mod;
	printf( "%lld\n", ans );
	return 0;
}

RGB Coloring

description

solution

将绿色 A + B A+B A+B看成既涂了红色 A A A,又涂了蓝色 B B B,则红色和蓝色格子彼此独立,就算涂在同一个格子上也没关系

枚举红色格子的数量 i i i,计算得出蓝色格子数量 j = K − A × i B j=\frac{K-A\times i}{B} j=BKA×i

判断格子数量为整数且都在 n n n以内即可,然后用组合计数从 n n n个格子中选红/蓝色

∑ C n i × C n j \sum C_n^i\times C_n^j Cni×Cnj

code

#include <cstdio>
#define int long long
#define mod 998244353
#define maxn 300005
int n, A, B, k;
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( m < 0 || n < m ) return 0;
	else return fac[n] * inv[m] % mod * inv[n - m] % mod;
}

signed main() {
	scanf( "%lld %lld %lld %lld", &n, &A, &B, &k );
	init();
	int ans = 0; 
	for( int i = 0, j;i <= n;i ++ ) {
		if( ( k - A * i ) % B ) continue;
		else j = ( k - A * i ) / B;
		ans = ( ans + C( n, i ) * C( n, j ) ) % mod;
	}
	printf( "%lld\n", ans );
	return 0;
}

123 Triangle

description

solution

N > 1 N>1 N>1式子是差分形式,后面序列只有能是 0 / 1 / 2 0/1/2 0/1/2

(除非 N = 1 N=1 N=1的情况答案有可能是 3 3 3

如果此时序列有 1 1 1,那么答案一定只能是 0 / 1 0/1 0/1,因为 0 / 2 0/2 0/2碰到 1 1 1都会变成 1 1 1

如果序列没有 1 1 1,则答案只会是 0 / 2 0/2 0/2,对于这种情况,将每个数 / 2 /2 /2得到结果后再 × 2 \times 2 ×2是等价的

所以现在的答案都只会是 0 / 1 0/1 0/1,考虑在 ( m o d 2 ) \pmod 2 (mod2)意义下做

( m o d 2 ) \pmod 2 (mod2)的情况下,加减法可以看成二进制的异或

所求即为一段序列相邻两个数异或直到最后成一个数的答案

考虑每个数被异或的次数,这其实是个倒杨辉三角 ( 0 , 0 ) (0,0) (0,0)开始

长为 n n n的序列的第 i i i个元素被异或的次数为 C n − 1 i − 1 C_{n-1}^{i-1} Cn1i1

计算在模 2 2 2意义下(除法不一定有逆元)的组合数

  • 计算每个阶乘中分解 2 2 2的次数,将除法变成减法,对于组合数最后剩下的 2 2 2的次数为 0 0 0,则为 1 ( m o d 2 ) 1\pmod 2 1(mod2),反之 0 ( m o d 2 ) 0\pmod 2 0(mod2)
  • 阶乘的分解,拆分成对每个数求出分解中 2 2 2的次数,做前缀和

code

#include <cmath>
#include <cstdio>
#define maxn 1000005
int n;
char s[maxn];
int x[maxn], cnt[maxn]; 

int main() {
	scanf( "%d %s", &n, s + 1 );
	n --;
	for( int i = 1;i <= n;i ++ )
		x[i] = fabs( s[i] - s[i + 1] );
	bool flag = 1;
	for( int i = 1;i <= n;i ++ )
		if( x[i] == 1 ) { flag = 0; break; }
	if( flag ) 
		for( int i = 1;i <= n;i ++ )
			x[i] >>= 1;
	for( int i = 1;i <= n;i ++ ) {
		int t = i;
		while( ! ( t & 1 ) ) t >>= 1, cnt[i] ++;
		cnt[i] += cnt[i - 1];
	}
	int ans = 0;
	for( int i = 1;i <= n;i ++ )
		ans ^= cnt[n - 1] - cnt[i - 1] - cnt[n - i] ? 0 : ( x[i] & 1 );
	if( flag ) ans <<= 1;
	printf( "%d", ans );
	return 0;
} 

[SDOI2016]排列计数

description

solution

组合数从 n n n个中选 m m m个强制 a i = i a_i=i ai=i,剩下的 n − m n-m nm a i ≠ i a_i≠i ai=i,就是 n − m n-m nm的错排数

code

#include <cstdio>
#define mod 1000000007
#define int long long
#define maxn 1000005
int fac[maxn], inv[maxn], D[maxn];
int T, 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() {
	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;
	D[0] = 0, D[1] = 0, D[2] = 1;
	for( int i = 3;i < maxn;i ++ )
		D[i] = ( D[i - 1] + D[i - 2] ) * ( i - 1 ) % mod;
}

int C( int n, int m ) {
	return fac[n] * inv[m] % mod * inv[n - m] % mod;
}

signed main() {
	init();
	scanf( "%lld", &T );
	while( T -- ) {
		scanf( "%lld %lld", &n, &m );
		if( n == m ) printf( "1\n" ); 
		else printf( "%lld\n", C( n, m ) * D[n - m] % mod );
	}
	return 0;
}

[HNOI2012]排队

description

solution

不给模数还明示答案可能很大,赤裸裸的大整数胁迫!!居心叵测!!

两名老师真的是很烦了,没有这么讨人厌的老师,可以直接女生插板法了

  • 两名老师之间是男生

    先排男生 A n n A_n^n Ann,产生 n + 1 n+1 n+1个空

    再插板两名老师, A n + 1 2 A_{n+1}^2 An+12,产生 n + 3 n+3 n+3个空

    最后插板 m m m名女生, A n + 3 m A_{n+3}^m An+3m

  • 两名老师之间是女生

    先排男生 A n n A_n^n Ann

    再打包两名老师放同一个隔板里,老师间还有顺序, A 2 2 C n + 1 1 A_2^2C_{n+1}^1 A22Cn+11

    先强制选一名女生放进老师中间 m m m,剩下的就隔板插

    男生 n n n个,老师和强制女生打包装成一个,总共是 n + 1 n+1 n+1个,产生 n + 2 n+2 n+2个空, A n + 2 m − 1 A_{n+2}^{m-1} An+2m1

综上, a n s = A n n A n + 1 2 A n + 3 m + A 2 2 C n + 1 1 A n + 2 m − 1 ans=A_n^nA_{n+1}^2A_{n+3}^m+A_2^2C_{n+1}^1A_{n+2}^{m-1} ans=AnnAn+12An+3m+A22Cn+11An+2m1

同样,老师不相邻 = = =不考虑老师 − - 老师相邻

  • 不考虑老师

    把老师当成男的,女的直接插, A n + 2 n + 2 A n + 3 m A_{n+2}^{n+2}A_{n+3}^m An+2n+2An+3m

  • 老师相邻

    捆绑法强制两名老师一起当成一个男的 A 2 2 A n + 1 n + 1 A n + 2 m A_2^2A_{n+1}^{n+1}A_{n+2}^m A22An+1n+1An+2m

二者做差就是答案
在这里插入图片描述

code

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int n, m;

struct Int {
	int g[20000], 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 ) {
		Int ans;
		ans.len = len;
		for( int i = 0;i < t.len;i ++ ) {
			ans.g[i] = g[i] - t.g[i];
			if( ans.g[i] < 0 ) ans.g[i] += 10, g[i + 1] --;
		}
		for( int i = t.len;i < len;i ++ )
			ans.g[i] = g[i]; 
		ans.clean();
		return ans;
	}
	
	Int operator * ( Int t ) {
		Int ans;
		ans.len = len + t.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] );
	}
	
};

Int calc( int n, int m ) {
	Int ans = ( 1 );
	for( int i = n - m + 1;i <= n;i ++ )
		ans = ans * i;
	return ans;
}

int main() {
	scanf( "%d %d", &n, &m );
	Int ans;
	ans = calc( n + 2, n + 2 ) * calc( n + 3, m ) - calc( 2, 2 ) * calc( n + 1, n + 1 ) * calc( n + 2, m );	
	ans.print();
	return 0;
}

[HNOI2011]卡农

description

solution

同种音乐其实通过除以 m ! m! m!就可以消掉

相当于在 S S S中选 m m m个子集,满足

  • 子集非空
  • 不存在两个完全一样的子集
  • ∀ i , i ∈ [ 1 , n ] \forall_{i,i\in[1,n]} i,i[1,n]每个元素出现次数为偶数

f i : f_i: fi: 考虑 i i i个子集,满足以上所有性质的方案数

  • 如果知道 i − 1 i-1 i1个子集的,那么 i i i个子集的集合就被确定了下来 A 2 n − 1 i − 1 A_{2^n-1}^{i-1} A2n1i1
  • 如果子集是空,那么去掉这个子集剩下的 i − 1 i-1 i1个子集是合法方案 f i − 1 f_{i-1} fi1
  • 如果 i i i子集与 j j j子集重复, j j j i − 1 i-1 i1种选择,剔除这两个子集剩下 i − 2 i-2 i2子集是合法的 f i − 2 f_{i-2} fi2 i i i子集选择有 2 n − 1 − ( i − 2 ) 2^n-1-(i-2) 2n1(i2)(排除掉剩下 i − 2 i-2 i2个子集,不能与之重复)

在这里插入图片描述

最后别忘了 m ! m! m!

code

#include <cstdio>
#define mod 100000007
#define int long long
#define maxn 1000005
int n, m;
int A[maxn], f[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() {
	scanf( "%lld %lld", &n, &m );
	f[0] = 1, f[1] = 0;
	int t = ( qkpow( 2, n ) - 1 + mod ) % mod;
	A[0] = 1;
	for( int i = 1;i <= m;i ++ )
		A[i] = ( A[i - 1] * ( t - i + 1 ) % mod + mod ) % mod;
	int MS = 1;
	for( int i = 2;i <= m;i ++ ) {
		f[i] = ( A[i - 1] - f[i - 1] - f[i - 2] * ( i - 1 ) % mod * ( t - i + 2 ) % mod ) % mod;
		MS = MS * i % mod;
	}
	printf( "%lld\n", ( f[m] + mod ) % mod * qkpow( MS, mod - 2 ) % mod );
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值