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(i−1,j)+i∗f(i−1,j−1)
看到 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(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=0j−1f(k,j−1)∗(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
n≤80
如果我们把非树边打一个标记,那么答案就是有
≤
k
\le k
≤k 条标记边的生成树个数
考虑到矩阵树定理求的是所有生成树边权的乘积,我们把非树边边权设为 x,树边设为 1
最后的答案就是
[
x
k
]
,
[
x
k
−
1
]
.
.
.
[x^k],[x^{k-1}]...
[xk],[xk−1]... 的系数和
考虑到这是一个
n
−
1
n-1
n−1 次的多项式,我们带
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=0∣∣a[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,1∗2j−1,
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,1∗2j−1
分这一位要选偶数还是奇数个 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,1∗2j−1
这一位必须选偶数个 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,0∗2j
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,0∗2j
#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(n−i),1=(k−1)n+(−1)n(k−1)
考虑旋转同构的情况,假设往后转 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=n1∑i=1nf(gcd(i,n))=n1∑d∣nf(d)ϕ(dn)
[HNOI2009]图的同构记数
首先置换个数是
n
!
n!
n!,考虑求每种置换的不动点个数
分两种情况讨论:
1.一条边的两端在同一个循环里
2.不在一个循环里
如果在一个循环里的话,考虑一条跨越
k
k
k 个点的边,本质不同的边的个数是
n
2
\frac{n}{2}
2n
因为
k
k
k 与
n
−
k
n-k
n−k 是等价的
如果不在一个循环,设两个循环的大小为
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=1mai∗∏cnti!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;
}