「SOL」行列式 (模拟赛)
1. 题面
有一个大小为
求
2. 解析
2.1. 拆分矩阵
和另外一道题很像……方阵的大多数位置都是
于是可以得到一个稀疏方阵
考虑行列式
2.2. 树的情况
不妨先假设
于是我们尝试把行列式的求解搬到树上来。行列式计算时可以看作每行每列恰好选一个元素,那么选择的
这样的“树”上,有向环只可能是父亲与儿子的二元环以及自环。我们可以做一个树形 DP 来把每个点划分到一个环中并计算贡献。但还有一个问题,行列式还有
更进一步的,
的奇偶性和「 减去环个数」的奇偶性相同,这样每新增一个环就乘上 ,更加方便树形 DP。
2.3. 非树边
现在考虑另一个方阵
观察行列式的定义式:
每个
接下来就是一些数学的分析,想到这一步可能需要一些经验吧……
如果在一个选择环边的方案中选择了两条权为

由于交换操作是可逆的,这两张图一一对应,而贡献系数相反,会被抵消。这也是为什么一开始要分离出一个全为
于是我们只需要考虑至多选择了一条
3. 小结
矩阵大多数位置值一样时可以拆成一个稀疏矩阵
求解行列式又多了一个新方法了 awa:
- 当稀疏矩阵
“特别稀疏”时可以状压 DP; - 也可以把矩阵看成邻接矩阵,此题保证了
,所以邻接矩阵是一棵树。
应该更注意矩阵的对称性,此题
4. 参考代码
点击展开/折叠 特别丑的参考代码
Copy/* Lucky_Glass */
#include <cstdio>
#include <cstring>
#include <cassert>
#include <algorithm>
const int MOD = 1e9 + 7;
inline int add(int a, const int &b) { return (a += b) >= MOD ? a - MOD : a; }
inline int sub(int a, const int &b) { return (a -= b) < 0 ? a + MOD : a; }
inline int mul(const int &a, const int &b) { return int(1ll * a * b % MOD); }
int pPow(int a, int b) {
int r = 1;
while (b) {
if (b & 1) r = mul(r, a);
a = mul(a, a), b >>= 1;
}
return r;
}
#define OPERON(a, b, fun) a = fun(a, b)
const int N = 1e6 + 10;
struct Graph {
int head[N], to[N << 1], nxt[N << 1], val[N << 1];
int edg_cnt;
inline void addEdge(const int &u, const int &v, const int &l) {
int p = ++edg_cnt;
to[p] = v, val[p] = l;
nxt[p] = head[u], head[u] = p;
}
inline int operator [] (const int &u) const { return head[u]; }
Graph() { edg_cnt = 1; }
} gr;
int n, valx;
int vald[N];
int f[N][4][2];
void dfs(const int &u, const int &fa) {
int u_emp[2] = {1, 0}, u_use[2] = {}, u_up[2] = {}, u_dn[2] = {};
for (int it = gr[u]; it; it = gr.nxt[it]) if (gr.to[it] != fa) {
int v = gr.to[it]; dfs(v, u);
int tmp_emp[2] = {}, tmp_use[2] = {}, tmp_up[2] = {}, tmp_dn[2] = {};
int tov = gr.val[it], tou = gr.val[it ^ 1];
/* empty + empty */
OPERON(tmp_emp[0], mul(u_emp[0], f[v][0][0]), add);
OPERON(tmp_emp[1], mul(u_emp[1], f[v][0][0]), add);
OPERON(tmp_emp[1], mul(u_emp[0], f[v][0][1]), add);
/* two-point loop */
OPERON(tmp_use[0], mul(mul(u_emp[0], f[v][1][0]), mul(tov, tou)), sub);
OPERON(tmp_use[1], mul(mul(u_emp[1], f[v][1][0]), mul(tov, tou)), sub);
OPERON(tmp_use[1], mul(mul(u_emp[0], f[v][1][1]), mul(tov, tou)), sub);
/* used + empty */
OPERON(tmp_use[0], mul(u_use[0], f[v][0][0]), add);
OPERON(tmp_use[1], mul(u_use[1], f[v][0][0]), add);
OPERON(tmp_use[1], mul(u_use[0], f[v][0][1]), add);
/* up */
OPERON(tmp_use[1], mul(mul(u_emp[0], f[v][2][0]), mul(valx, tou)), sub);
/* down */
OPERON(tmp_use[1], mul(mul(u_emp[0], f[v][3][0]), mul(valx, tov)), sub);
/* lca */
OPERON(tmp_use[1], mul(mul(u_up[0], f[v][3][0]), mul(tov, valx)), sub);
OPERON(tmp_use[1], mul(mul(u_dn[0], f[v][2][0]), mul(tou, valx)), sub);
/* go up */
OPERON(tmp_up[0], mul(mul(u_emp[0], f[v][2][0]), tou), add);
OPERON(tmp_up[1], mul(mul(u_emp[1], f[v][2][0]), tou), add);
OPERON(tmp_up[1], mul(mul(u_emp[0], f[v][2][1]), tou), add);
/* up + empty */
OPERON(tmp_up[0], mul(u_up[0], f[v][0][0]), add);
OPERON(tmp_up[1], mul(u_up[1], f[v][0][0]), add);
OPERON(tmp_up[1], mul(u_up[0], f[v][0][1]), add);
/* go down */
OPERON(tmp_dn[0], mul(mul(u_emp[0], f[v][3][0]), tov), add);
OPERON(tmp_dn[1], mul(mul(u_emp[1], f[v][3][0]), tov), add);
OPERON(tmp_dn[1], mul(mul(u_emp[0], f[v][3][1]), tov), add);
/* down + empty */
OPERON(tmp_dn[0], mul(u_dn[0], f[v][0][0]), add);
OPERON(tmp_dn[1], mul(u_dn[1], f[v][0][0]), add);
OPERON(tmp_dn[1], mul(u_dn[0], f[v][0][1]), add);
u_emp[0] = tmp_emp[0], u_emp[1] = tmp_emp[1];
u_use[0] = tmp_use[0], u_use[1] = tmp_use[1];
u_up[0] = tmp_up[0], u_up[1] = tmp_up[1];
u_dn[0] = tmp_dn[0], u_dn[1] = tmp_dn[1];
}
/* self loop */
OPERON(f[u][0][1], mul(u_emp[0], valx), sub);
OPERON(f[u][0][1], mul(u_emp[1], vald[u]), sub);
OPERON(f[u][0][0], mul(u_emp[0], vald[u]), sub);
/* others */
OPERON(f[u][0][0], u_use[0], add);
OPERON(f[u][0][1], u_use[1], add);
/* two-point loop with fa */
OPERON(f[u][1][0], u_emp[0], add);
OPERON(f[u][1][1], u_emp[1], add);
/* go up */
OPERON(f[u][2][0], u_up[0], add);
OPERON(f[u][2][1], u_up[1], add);
/* start from u */
OPERON(f[u][2][0], u_emp[0], add);
OPERON(f[u][2][1], u_emp[1], add);
/* go down */
OPERON(f[u][3][0], u_dn[0], add);
OPERON(f[u][3][1], u_dn[1], add);
/* end at u */
OPERON(f[u][3][0], u_emp[0], add);
OPERON(f[u][3][1], u_emp[1], add);
}
template<typename RType> RType rin(RType &r) {
int b = 1, c = getchar(); r = 0;
while (c < '0' || '9' < c) b = c == '-' ? -1 : b, c = getchar();
while ('0' <= c && c <= '9') r = r * 10 + (c ^ '0'), c = getchar();
return r *= b;
}
int main() {
rin(n), rin(valx);
for (int i = 1; i <= n; ++i) {
rin(vald[i]);
OPERON(vald[i], valx, sub);
}
for (int i = 2; i <= n; ++i) {
int fa, tofa, tou;
rin(fa), rin(tofa), rin(tou);
OPERON(tofa, valx, sub), OPERON(tou, valx, sub);
gr.addEdge(i, fa, tofa);
gr.addEdge(fa, i, tou);
}
dfs(1, 0);
int ans = add(f[1][0][0], f[1][0][1]);
if (n & 1) ans = sub(0, ans);
printf("%d\n", ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现