丝且人一口
- Virus Tree 2
- description
- solution
- code
- RGB Coloring
- description
- solution
- code
- 123 Triangle
- description
- solution
- code
- [SDOI2016]排列计数
- description
- solution
- code
- [HNOI2012]排队
- description
- solution
- code
- [HNOI2011]卡农
- description
- solution
- code
Virus Tree 2
description
solution
距离不超过222的点,可能是儿子/孙子/父亲/爷爷
考虑从上到下,由上面的颜色选取决定下面的颜色
显然,第一个点有KKK种选择,第二个点有K−1K-1K−1种,每次不同都要−1-1−1
答案就是每个点的选择的乘积
所以只需要把这种过程的递减通过dfs
树来实现
对于现在的子树根节点uuu,如果vvv是第一个儿子,那么需要考虑vvv有没有爷爷
如果不是第一个儿子,那么就是前一个儿子的方案数再−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+BA+BA+B看成既涂了红色AAA,又涂了蓝色BBB,则红色和蓝色格子彼此独立,就算涂在同一个格子上也没关系
枚举红色格子的数量iii,计算得出蓝色格子数量j=K−A×iBj=\frac{K-A\times i}{B}j=BK−A×i
判断格子数量为整数且都在nnn以内即可,然后用组合计数从nnn个格子中选红/蓝色
∑Cni×Cnj\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>1N>1N>1式子是差分形式,后面序列只有能是0/1/20/1/20/1/2
(除非N=1N=1N=1的情况答案有可能是333)
如果此时序列有111,那么答案一定只能是0/10/10/1,因为0/20/20/2碰到111都会变成111
如果序列没有111,则答案只会是0/20/20/2,对于这种情况,将每个数/2/2/2得到结果后再×2\times 2×2是等价的
所以现在的答案都只会是0/10/10/1,考虑在(mod2)\pmod 2(mod2)意义下做
在(mod2)\pmod 2(mod2)的情况下,加减法可以看成二进制的异或
所求即为一段序列相邻两个数异或直到最后成一个数的答案
考虑每个数被异或的次数,这其实是个倒杨辉三角(0,0)(0,0)(0,0)开始
长为nnn的序列的第iii个元素被异或的次数为Cn−1i−1C_{n-1}^{i-1}Cn−1i−1
计算在模222意义下(除法不一定有逆元)的组合数
- 计算每个阶乘中分解222的次数,将除法变成减法,对于组合数最后剩下的222的次数为000,则为1(mod2)1\pmod 21(mod2),反之0(mod2)0\pmod 20(mod2)
- 阶乘的分解,拆分成对每个数求出分解中222的次数,做前缀和
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
组合数从nnn个中选mmm个强制ai=ia_i=iai=i,剩下的n−mn-mn−m个ai≠ia_i≠iai=i,就是n−mn-mn−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
不给模数还明示答案可能很大,赤裸裸的大整数胁迫!!居心叵测!!
两名老师真的是很烦了,没有这么讨人厌的老师,可以直接女生插板法了
-
两名老师之间是男生
先排男生AnnA_n^nAnn,产生n+1n+1n+1个空
再插板两名老师,An+12A_{n+1}^2An+12,产生n+3n+3n+3个空
最后插板mmm名女生,An+3mA_{n+3}^mAn+3m
-
两名老师之间是女生
先排男生AnnA_n^nAnn
再打包两名老师放同一个隔板里,老师间还有顺序,A22Cn+11A_2^2C_{n+1}^1A22Cn+11
先强制选一名女生放进老师中间mmm,剩下的就隔板插
男生nnn个,老师和强制女生打包装成一个,总共是n+1n+1n+1个,产生n+2n+2n+2个空,An+2m−1A_{n+2}^{m-1}An+2m−1
综上,ans=AnnAn+12An+3m+A22Cn+11An+2m−1ans=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
同样,老师不相邻===不考虑老师−-−老师相邻
-
不考虑老师
把老师当成男的,女的直接插,An+2n+2An+3mA_{n+2}^{n+2}A_{n+3}^mAn+2n+2An+3m
-
老师相邻
捆绑法强制两名老师一起当成一个男的A22An+1n+1An+2mA_2^2A_{n+1}^{n+1}A_{n+2}^mA22An+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!就可以消掉
相当于在SSS中选mmm个子集,满足
- 子集非空
- 不存在两个完全一样的子集
- ∀i,i∈[1,n]\forall_{i,i\in[1,n]}∀i,i∈[1,n]每个元素出现次数为偶数
设fi:f_i:fi: 考虑iii个子集,满足以上所有性质的方案数
- 如果知道i−1i-1i−1个子集的,那么iii个子集的集合就被确定了下来A2n−1i−1A_{2^n-1}^{i-1}A2n−1i−1
- 如果子集是空,那么去掉这个子集剩下的i−1i-1i−1个子集是合法方案fi−1f_{i-1}fi−1
- 如果iii子集与jjj子集重复,jjj有i−1i-1i−1种选择,剔除这两个子集剩下i−2i-2i−2子集是合法的fi−2f_{i-2}fi−2,iii子集选择有2n−1−(i−2)2^n-1-(i-2)2n−1−(i−2)(排除掉剩下i−2i-2i−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;
}