暑假集训 ---- 数学常识!

BZOJ 2655 calc
先弄出答案的生成函数
∏ ( 1 + a 1 x ) ( 1 + a 2 x ) ( 1 + a 3 x ) . . . ( 1 + a A x ) \prod (1+a_1x)(1+a_2x)(1+a_3x)...(1+a_A x) (1+a1x)(1+a2x)(1+a3x)...(1+aAx)
这个多项式的第 n 项就是答案
考虑DP来求, f i , j f_{i,j} fi,j 表示考虑到 a i ai ai, 多项式第 j 项 的值
f ( i , j ) = f ( i − 1 , j ) + i ∗ f ( i − 1 , j − 1 ) f(i,j)=f(i-1,j)+i * f(i-1,j-1) f(i,j)=f(i1,j)+if(i1,j1)
看到 A 那么大, 应该想到拉格朗日差值, 但这个东西是个多项式吗, 继续化简看看
f ( i , j ) = f ( i − 1 , j ) + i ∗ f ( i − 1 , j − 1 ) = f ( i − 2 , j ) + ( i − 1 ) ∗ f ( i − 2 , j − 1 ) + i ∗ f ( i − 1 , j − 1 ) = . . . = ∑ k = 0 j − 1 f ( k , j − 1 ) ∗ ( k + 1 ) f(i,j)=f(i-1,j)+i * f(i-1,j-1) = f(i-2, j) + (i-1) * f(i-2,j-1) + i * f(i-1, j-1)=...=\sum_{k=0}^{j-1}f(k, j-1)*(k+1) f(i,j)=f(i1,j)+if(i1,j1)=f(i2,j)+(i1)f(i2,j1)+if(i1,j1)=...=k=0j1f(k,j1)(k+1)
多项式就比较明显可以看出来, 乘一个(k+1) 会多一次, 求一个前缀和会多一次
所以答案是一个 n * 2 次的多项式


树:
给一棵n 个节点的树,节点分别编号为0 到 n−1。
你可以通过如下的操作来修改这棵树:首先先删去树上的一条边,此时树会分裂为两个连通块,然后在两个连通块之间加上一条新的边使得它们变成一棵新的树。
问有多少棵 n 个节点的树可以通过对原树进行不超过 k 次这样的操作来得到,答案对 1 e 9 + 7 1e9 + 7 1e9+7 取模。如果有一条边 (u, v) 出现在了树 A 中且不在树B 中,我们就认为树 A 和树 B 是不同的。 n ≤ 80 n ≤ 80 n80
如果我们把非树边打一个标记,那么答案就是有 ≤ k \le k k 条标记边的生成树个数
考虑到矩阵树定理求的是所有生成树边权的乘积,我们把非树边边权设为 x,树边设为 1
最后的答案就是 [ x k ] , [ x k − 1 ] . . . [x^k],[x^{k-1}]... [xk],[xk1]... 的系数和
考虑到这是一个 n − 1 n-1 n1 次的多项式,我们带 n n n 个值进去就可以高斯消元解把系数解出来了
复杂度 O ( n 4 ) O(n^4) O(n4)

#include<bits/stdc++.h>
#define N 85
using namespace std;
const int Mod = 1e9 + 7;
typedef long long ll;
ll add(ll a, ll b){ return (a + b) % Mod;}
ll mul(ll a, ll b){ return (a * b) % Mod;}
ll power(ll a, ll b){ ll ans = 1;
	for(;b;b>>=1){ if(b&1) ans = mul(ans, a); a = mul(a, a);}
	return ans;
}
int n, m, k;
int mp[N][N];
ll a[N][N], c[N][N];
ll calc(int v){
	memset(a, 0, sizeof(a));
	for(int i = 1; i <= n; i++){
		for(int j = i+1; j <= n; j++){
			if(mp[i][j]) a[i][j] = a[j][i] = Mod-1, ++a[i][i], ++a[j][j];
			else a[i][j] = a[j][i] = Mod - v, a[i][i] += v, a[j][j] += v;
		}
	}
	ll ans = 1;
	for(int i = 1; i < n; i++){
		int k = i; for(;k < n; k++) if(a[k][i]) break;
		if(k >= n) return 0;
		if(k ^ i){ ans = Mod - ans; swap(a[k], a[i]);}
		ans = mul(ans, a[i][i]);
		ll inv = power(a[i][i], Mod - 2);
		for(int j = i; j < n; j++) a[i][j] = mul(a[i][j], inv);
		for(int j = i+1; j < n; j++){
			ll now = a[j][i];
			for(int k = i; k < n; k++) a[j][k] = add(a[j][k], Mod - mul(a[i][k], now));
		}
	} return ans;
}
void gauss(){
	for(int i = 1; i <= n; i++){
		int k = i; for(;k <= n; k++) if(c[k][i]) break;
		if(k > n) continue;
		if(k ^ i) swap(c[k], c[i]);
		ll inv = power(c[i][i], Mod - 2);
		for(int j = i; j <= n+1; j++) c[i][j] = mul(c[i][j], inv);
		for(int j = i+1; j <= n; j++){
			ll now = c[j][i];
			for(int k = i; k <= n+1; k++) c[j][k] = add(c[j][k], Mod - mul(c[i][k], now));
		}
	} 
	for(int i = n; i >= 1; i--)
		for(int j = i+1; j <= n; j++) 
			c[i][n+1] = add(c[i][n+1], Mod - mul(c[i][j], c[j][n+1]));
}
int main(){
	scanf("%d%d", &n, &k);
	for(int i = 2; i <= n; i++){
		int x; scanf("%d", &x); mp[x+1][i] = mp[i][x+1] = 1;
	} 
	for(int i = 1; i <= n; i++){
		ll tmp = 1;
		for(int j = 1; j <= n; j++) c[i][j] = tmp, tmp = mul(tmp, i);
		c[i][n+1] = calc(i);
	}
	gauss(); ll ans = 0;
	for(int i = 1; i <= k+1; i++) ans = add(ans, c[i][n+1]);
	cout << ans << '\n'; return 0;
}

hiho1512 生成树计数
考虑 ( x 1 + x 2 + . . . + x n ) k (x_1+x_2+...+x_n)^k (x1+x2+...+xn)k 的组合意义,就是任选 k 个乘起来
上生成函数,带标号 ,把 w w w 换成 ∑ ( w x ) i i ! \sum\frac{(wx)^i}{i!} i!(wx)i,矩阵树出来的第 k k k 项系数 ∗ k ! *k! k!就是答案
是一个 n k nk nk 次多项式,暴力高斯消元要凉,好像是个什么重心拉格朗日插值,咕咕咕 传送门
51nod1312 最大异或和
首先不在线性基里面的可以异或成最大异或和
考虑怎么才能使线性基里面的数达到最大
先高斯消元,然后将最大基变成最大异或和
然后对于其它的贪心,显然自己异或上最大异或和就是最大的

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll b[70], c[70], mx; int cnt;
int n; ll ans;
void ins(ll x){
	for(int i = 55; i >= 0; i--){
		if((x >> i) & 1){
			if(b[i]) x ^= b[i];
			else{ b[i] = x; break;}
		}
	}
}
int main(){
	scanf("%d", &n);
	for(int i = 1; i <= n; i++){ ll a; scanf("%lld", &a); ins(a);}
	for(int i = 55; i >= 0; i--){
		for(int j = i-1; j >= 0; j--) if((b[i] >> j) & 1) b[i] ^= b[j];
	}
	for(int i = 55; i >= 0; i--) if(b[i]) c[++cnt] = b[i];
	for(int i = cnt; i >= 1; i--) mx ^= c[i];
	ans = (n - cnt + 1) * mx;
	for(int i = 2; i <= cnt; i++) ans += mx ^ c[i];
	cout << ans; return 0;
}

CF228D
把合法的集合插进去就是一个线性基
一个集合可以对应多个线性基
我们考虑如何让一个集合只对应一个线性基,发现一个集合只对应唯一高斯消元后的上三角矩阵
于是我们可以对每一个上三角矩阵求出它可以还原出多少个集合
有一个性质就是,线性基表示的最大的数就是消成上三角矩阵后的异或和
考虑 dp, f i , j , 0 / 1 f_{i,j,0/1} fi,j,0/1 表示到第 i 位,选了 j 个在线性基中为 1,有没有超过 k 的方案数
如果要选进线性基,需要满足之前没有满或当前可以选1,并且之前的基上这一位为 0
f i + 1 , j + 1 , k + = f i , j , k ( k = 0 ∣ ∣ a [ i + 1 ] = 1 ) f_{i+1,j+1,k}+=f_{i,j,k}(k=0||a[i+1]=1) fi+1,j+1,k+=fi,j,k(k=0a[i+1]=1)
如果当前不选进线性基
1.如果之前满了,当前位为1 f i + 1 , j , 0 + = f i , j , 1 ∗ 2 j − 1 f_{i+1,j,0}+=f_{i,j,1}*2^{j-1} fi+1,j,0+=fi,j,12j1, f i + 1 , j , 1 + = f i , j , 1 ∗ 2 j − 1 f_{i+1,j,1}+=f_{i,j,1}*2^{j-1} fi+1,j,1+=fi,j,12j1
分这一位要选偶数还是奇数个 1 讨论
2.如果之前满了,当前位为0 f i + 1 , j , 1 + = f i , j , 1 ∗ 2 j − 1 f_{i+1,j,1}+=f_{i,j,1}*2^{j-1} fi+1,j,1+=fi,j,12j1
这一位必须选偶数个 1
3.如果之前没有满,当前位为1 f i + 1 , j , 0 + = f i , j , 0 ∗ 2 j f_{i+1, j, 0}+=f_{i,j,0}*2^j fi+1,j,0+=fi,j,02j
4.如果之前没有满,当前位为0 f i + 1 , j , 0 + = f i , j , 0 ∗ 2 j f_{i+1,j,0}+=f_{i,j,0}*2^j fi+1,j,0+=fi,j,02j

#include<bits/stdc++.h>
#define N 40
using namespace std;
const int Mod = 1e9 + 7;
typedef long long ll;
ll f[N][N][2], len, lim[N], n, ans;
ll mul(ll a, ll b){ return (a * b) % Mod;}
ll add(ll a, ll b){ return (a + b) % Mod;}
void Add(ll &a, ll b){ a = add(a, b);}
int main(){
	scanf("%d", &n);
	if(n == 0) { cout << "1"; return 0; }
	while(n){ lim[++len] = n & 1; n >>= 1; }
	reverse(lim + 1, lim + len + 1);
	f[0][0][1] = 1;
	for(int i = 0; i < len; i++) for(int j = 0; j <= i; j++)
		for(int k = 0; k < 2; k++) if(f[i][j][k]){
			Add(f[i+1][j][k & (lim[i+1]^1)], mul(f[i][j][k], j ? (1<<j-1) : 1));
			if(!k || lim[i+1]){
				if(j) Add(f[i+1][j][k], mul(f[i][j][k], 1<<j-1));
				Add(f[i+1][j+1][k], f[i][j][k]);
			}
		}
	ll ans = 0;
	for(int i = 0; i <= len; i++) Add(ans, add(f[len][i][0], f[len][i][1]));
	cout << ans; return 0;
}

WOJ4686 尘封的花环
暴力枚举有几个不满足限制,即与后面一个点相邻,容斥
f ( n ) = ∑ i = 0 n ( − 1 ) i ( n i ) k m a x ( n − i ) , 1 = ( k − 1 ) n + ( − 1 ) n ( k − 1 ) f(n)=\sum_{i=0}^n(-1)^i\binom{n}{i}k^{max(n-i),1}=(k-1)^n+(-1)^n(k-1) f(n)=i=0n(1)i(in)kmax(ni),1=(k1)n+(1)n(k1)
考虑旋转同构的情况,假设往后转 i 个结点,会产生 g c d ( n , i ) gcd(n,i) gcd(n,i) 个循环,算不动点的个数需要让每个循环颜色相同 ,于是不动点个数相当于在一个大小为 g c d ( n , i ) gcd(n, i) gcd(n,i) 的环的答案,即 f ( g c d ( n , i ) ) f(gcd(n,i)) f(gcd(n,i))
a n s = 1 n ∑ i = 1 n f ( g c d ( i , n ) ) = 1 n ∑ d ∣ n f ( d ) ϕ ( n d ) ans=\frac{1}{n}\sum_{i=1}^nf(gcd(i,n))=\frac{1}{n}\sum_{d|n}f(d)\phi(\frac{n}{d}) ans=n1i=1nf(gcd(i,n))=n1dnf(d)ϕ(dn)
[HNOI2009]图的同构记数
首先置换个数是 n ! n! n!,考虑求每种置换的不动点个数
分两种情况讨论:
1.一条边的两端在同一个循环里
2.不在一个循环里

如果在一个循环里的话,考虑一条跨越 k k k 个点的边,本质不同的边的个数是 n 2 \frac{n}{2} 2n
因为 k k k n − k n-k nk 是等价的
如果不在一个循环,设两个循环的大小为 n , m n,m n,m,两个端点在环上走,走到起始位置经过的边数为 l c m ( n , m ) lcm(n,m) lcm(n,m),而边的总数为 n m nm nm,所有本质不同的边的个数为 g c d ( n , m ) gcd(n,m) gcd(n,m)
考虑不动点的个数,一条边如果有,那么它附属的 l c m ( n , m ) lcm(n,m) lcm(n,m) 条边也必须有
所以不动点个数等价于考虑这 g c d ( n , m ) gcd(n,m) gcd(n,m) 条边的情况,方案数即为 2 g c d ( n , m ) 2^{gcd(n,m)} 2gcd(n,m)
然后发现只与循环个数与循环大小有关,可以暴力枚举 n 的拆分, a i a_i ai 表示循环大小 c n t i cnt_i cnti 表示大小为 i 的个数
一个拆分的方案数为
n ! ∏ i = 1 m a i ∗ ∏ c n t i ! \frac{n!}{\prod_{i=1}^ma_i*\prod cnt_i!} i=1maicnti!n!
因为一个循环是一个环,大小相同的循环之间无序

#include<bits/stdc++.h>
#define N 65
using namespace std;
const int Mod = 997;
int add(int a, int b){ return a + b >= Mod ? a + b - Mod : a + b;}
int mul(int a, int b){ return a * b % Mod;}
int n, num[N], ans;
int fac[N], inv[Mod + 10], pw[Mod + 10], g[N][N];
int power(int a, int b){ int ans = 1; for(;b;b>>=1,a=mul(a,a)) if(b&1) ans=mul(ans,a); return ans;}
int gcd(int a, int b){ return !b ? a : gcd(b, a % b);}
void dfs(int u, int rest){
	if(!rest){
		int now = fac[n];
		int cnt = 0;
		for(int i = u + 1; i <= n; i++){
			now = mul(now, inv[mul(fac[num[i]], power(i, num[i]))]);
			cnt += i / 2 * num[i];
			for(int j = i + 1; j <= n; j++){
				cnt += g[i][j] * num[i] * num[j];
			} cnt += i * num[i] * (num[i] - 1) / 2;
		} ans = add(ans, mul(now, pw[cnt % (Mod-1)]));
		return;
	}
	if(!u) return;
	num[u] = 0;
	dfs(u - 1, rest);
	for(int i = 1; i * u <= rest; i++){
		num[u] = i; dfs(u - 1, rest - i * u); num[u] = 0;
	}
}
void prework(){
	fac[0] = fac[1] = inv[0] = inv[1] = 1;
	for(int i = 1; i <= n; i++) fac[i] = mul(fac[i-1], i);
	for(int i = 2; i < Mod; i++) inv[i] = mul(Mod-Mod/i, inv[Mod%i]);
	pw[0] = 1;
	for(int i = 1; i < Mod; i++) pw[i] = add(pw[i-1], pw[i-1]);
	for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) g[i][j] = gcd(i, j);
}
int main(){
	cin >> n;
	prework(); dfs(n, n); cout << mul(ans, power(fac[n], Mod - 2)); 
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FSYo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值