​BZOJ 2655 calc​

​​ 先弄出答案的生成函数
暑假集训 ---- 数学常识!_线性基
这个多项式的第 n 项就是答案
考虑DP来求, 暑假集训 ---- 数学常识!_线性基_02 表示考虑到 暑假集训 ---- 数学常识!_i++_03, 多项式第 j 项 的值
暑假集训 ---- 数学常识!_多项式_04
看到 A 那么大, 应该想到拉格朗日差值, 但这个东西是个多项式吗, 继续化简看看
暑假集训 ---- 数学常识!_i++_05
多项式就比较明显可以看出来, 乘一个(k+1) 会多一次, 求一个前缀和会多一次
所以答案是一个 n * 2 次的多项式


树:
给一棵n 个节点的树,节点分别编号为0 到 n−1。
你可以通过如下的操作来修改这棵树:首先先删去树上的一条边,此时树会分裂为两个连通块,然后在两个连通块之间加上一条新的边使得它们变成一棵新的树。
问有多少棵 n 个节点的树可以通过对原树进行不超过 k 次这样的操作来得到,答案对暑假集训 ---- 数学常识!_多项式_06 取模。如果有一条边 (u, v) 出现在了树 A 中且不在树B 中,我们就认为树 A 和树 B 是不同的。暑假集训 ---- 数学常识!_多项式_07
如果我们把非树边打一个标记,那么答案就是有 暑假集训 ---- 数学常识!_i++_08 条标记边的生成树个数
考虑到矩阵树定理求的是所有生成树边权的乘积,我们把非树边边权设为 x,树边设为 1
最后的答案就是 暑假集训 ---- 数学常识!_i++_09 的系数和
考虑到这是一个 暑假集训 ---- 数学常识!_多项式_10 次的多项式,我们带 暑假集训 ---- 数学常识!_多项式_11 个值进去就可以高斯消元解把系数解出来了
复杂度 暑假集训 ---- 数学常识!_i++_12

#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 生成树计数​

​​ 考虑 暑假集训 ---- 数学常识!_线性基_13 的组合意义,就是任选 k 个乘起来
上生成函数,带标号 ,把 暑假集训 ---- 数学常识!_线性基_14 换成 暑假集训 ---- 数学常识!_线性基_15,矩阵树出来的第 暑假集训 ---- 数学常识!_多项式_16 项系数暑假集训 ---- 数学常识!_i++_17就是答案
是一个 暑假集训 ---- 数学常识!_i++_18 次多项式,暴力高斯消元要凉,好像是个什么重心拉格朗日插值,咕咕咕 ​​​​

​传送门​​​​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,暑假集训 ---- 数学常识!_多项式_19 表示到第 i 位,选了 j 个在线性基中为 1,有没有超过 k 的方案数
如果要选进线性基,需要满足之前没有满或当前可以选1,并且之前的基上这一位为 0
暑假集训 ---- 数学常识!_线性基_20
如果当前不选进线性基
1.如果之前满了,当前位为1 暑假集训 ---- 数学常识!_线性基_21,暑假集训 ---- 数学常识!_i++_22
分这一位要选偶数还是奇数个 1 讨论
2.如果之前满了,当前位为0 暑假集训 ---- 数学常识!_i++_22
这一位必须选偶数个 1
3.如果之前没有满,当前位为1 暑假集训 ---- 数学常识!_i++_24
4.如果之前没有满,当前位为0 暑假集训 ---- 数学常识!_i++_25

#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 尘封的花环​

​​ 暴力枚举有几个不满足限制,即与后面一个点相邻,容斥
暑假集训 ---- 数学常识!_线性基_26
考虑旋转同构的情况,假设往后转 i 个结点,会产生 暑假集训 ---- 数学常识!_线性基_27 个循环,算不动点的个数需要让每个循环颜色相同 ,于是不动点个数相当于在一个大小为 暑假集训 ---- 数学常识!_i++_28 的环的答案,即暑假集训 ---- 数学常识!_线性基_29
暑假集训 ---- 数学常识!_线性基_30
​​​[HNOI2009]图的同构记数​

​​ 首先置换个数是 暑假集训 ---- 数学常识!_i++_31,考虑求每种置换的不动点个数
分两种情况讨论:
1.一条边的两端在同一个循环里
2.不在一个循环里

如果在一个循环里的话,考虑一条跨越 暑假集训 ---- 数学常识!_多项式_16 个点的边,本质不同的边的个数是 暑假集训 ---- 数学常识!_多项式_33
因为 暑假集训 ---- 数学常识!_多项式_16暑假集训 ---- 数学常识!_线性基_35 是等价的
如果不在一个循环,设两个循环的大小为 暑假集训 ---- 数学常识!_线性基_36,两个端点在环上走,走到起始位置经过的边数为 暑假集训 ---- 数学常识!_多项式_37,而边的总数为 暑假集训 ---- 数学常识!_线性基_38,所有本质不同的边的个数为 暑假集训 ---- 数学常识!_i++_39
考虑不动点的个数,一条边如果有,那么它附属的 暑假集训 ---- 数学常识!_多项式_37 条边也必须有
所以不动点个数等价于考虑这 暑假集训 ---- 数学常识!_i++_39 条边的情况,方案数即为 暑假集训 ---- 数学常识!_多项式_42
然后发现只与循环个数与循环大小有关,可以暴力枚举 n 的拆分,暑假集训 ---- 数学常识!_线性基_43 表示循环大小 暑假集训 ---- 数学常识!_线性基_44 表示大小为 i 的个数
一个拆分的方案数为
暑假集训 ---- 数学常识!_线性基_45
因为一个循环是一个环,大小相同的循环之间无序

#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;
}