丝且人一口
Virus Tree 2
description
solution
距离不超过 2 2 2的点,可能是儿子/孙子/父亲/爷爷
考虑从上到下,由上面的颜色选取决定下面的颜色
显然,第一个点有 K K K种选择,第二个点有 K − 1 K-1 K−1种,每次不同都要 − 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=BK−A×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} Cn−1i−1
计算在模 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 n−m个 a i ≠ i a_i≠i ai=i,就是 n − m n-m n−m的错排数
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+2m−1
综上, 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+2m−1
同样,老师不相邻 = = =不考虑老师 − - −老师相邻
-
不考虑老师
把老师当成男的,女的直接插, 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 i−1个子集的,那么 i i i个子集的集合就被确定了下来 A 2 n − 1 i − 1 A_{2^n-1}^{i-1} A2n−1i−1
- 如果子集是空,那么去掉这个子集剩下的 i − 1 i-1 i−1个子集是合法方案 f i − 1 f_{i-1} fi−1
- 如果 i i i子集与 j j j子集重复, j j j有 i − 1 i-1 i−1种选择,剔除这两个子集剩下 i − 2 i-2 i−2子集是合法的 f i − 2 f_{i-2} fi−2, i i i子集选择有 2 n − 1 − ( i − 2 ) 2^n-1-(i-2) 2n−1−(i−2)(排除掉剩下 i − 2 i-2 i−2个子集,不能与之重复)
最后别忘了 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;
}