闲话 25.3.(4.5)
闲话
大学生活?退休生活!
just cause 3 真好玩,大家都去玩 /cy
lean game 真好玩,大家都去玩 /cy
没钱买 prove u can win,大家可以玩 /cy
BTW 快来做 STARGAZERS
推歌:Explore by 晴一番p feat. 初音未来;倒瞰清白 by Creuzer et al.。
详细揭秘:什么是单项式多项式?
当 在 上秩为 的矩阵中均匀随机取值时,计算 中非零元素个数的期望。
,保证 为质数。
jjdw 版题解,就是删了很多不重要的内容。
虽然有期望,做法还是线性代数 + q-analog,但这题的难度真是省选 T1.5 吧!你要信我!
当不需要考虑权重时,计数秩为 的矩阵是 well-known 的,我们可以通过 的手法处理其中一些问题。可以知道, 中秩为 的矩阵数量为
证明:我们断言,若矩阵 的秩为 ,则定存在列满秩矩阵 与行满秩矩阵 ,使得 。
对断言的证明
为简便,记一个 的矩阵 为 ,并记 全零矩阵为 , 单位方阵为 。
由于初等变换不改变矩阵的秩,总存在可逆矩阵 使得
即
下面需要证明 的秩为 。
由秩的性质可知,。而矩阵的秩不可能大于矩阵行数、列数中更小的值,则有 。综合两个结论可知 。
此方法被称为满秩分解。
只知道存在 无法计数,我们还需要知道一个 对应了多少对 。自然可以知道 ,其中 为一个满秩矩阵。这样可以知道,一个 对应了 对彼此不同的 。分别计数 ,再除以 即可。
上面的证明给予了我们一定的启发。下面将 视作一个元素位于模 剩余类域 内的 矩阵处理,或 。要求的就是所有秩为 的矩阵 中非零元素的期望。
随机变量 也可以写成 ,其中 对位于 的元素定义: 当且仅当 位于 的元素非零。根据期望的线性性,可以将计算 转换为计算每个 并求和。
一个重要的观察是,对每个 , 的分布都是相同的。这是由于初等变换不改变矩阵的秩,因此我们可以考虑一个双射:将给定矩阵的第 行与第 行交换、第 列与第 列交换。这就将每个元素都与位于 位置的元素建立了等价关系,自然有上述观察。那么我们知道 的期望即为 。因此,不需要分析整个矩阵。
考虑一个矩阵的行简化阶梯形式,即进行消元后,所有零行都在最下方,剩余行最左侧元素均为 ,位置按阶梯形由左上角向右下角排布的形式。这形式可以用来产生行等价类;两个矩阵行等价说明二者可以通过一系列初等行变换互化,也表明二者的行向量空间相同。
容易知道,一个秩为 的 矩阵 的行简化阶梯形式有 个非零行。令 为这 行组成的 矩阵。可以知道 的行向量空间是 的向量空间的一组基,那么进行满秩分解,得到对应的矩阵 。
通过上面的表示方法,我们可以将秩为 的矩阵组成的集合表示为秩为 的 矩阵组成的集合与秩为 的 行简化阶梯矩阵组成的集合的笛卡尔积(或 和 间形成了双射)。这说明可以通过独立地选择 与 来生成 ,并且这样的生成是均匀随机的。
可以知道
由于 是行简化阶梯矩阵, 可能是 或 ,而 对 都为 。因此可以知道,。从而
是秩为 的 矩阵,因此 的第一列是任意长为 的非零向量,数量为 。这些非零向量中有 个向量首元素为 ,因此
和 携带的信息不同,应用的计算方式就不同。选择 就是在选择 的行向量空间。只要行向量空间中存在一个向量首项非零, 就成立。正难则反,先计算 的方案数。这指出所有向量的首项都为 ,因此可以认为这些向量都是 维的,在这其中选择一个 维子空间的方案数为
不难得到
因此可以知道
最终答案为 ,复杂度显然是 的。
提供一份暴力代码以验证正确性
#include <bits/stdc++.h>
using namespace std; using pii = pair<int,int>; using vi = vector<int>; using vp = vector<pii>; using ll = long long;
using ull = unsigned long long; using db = double; using ld = long double; using lll = __int128_t;
mt19937 rnd(chrono::steady_clock::now().time_since_epoch().count());
template <typename T> T rand(T l, T r) { return uniform_int_distribution<T>(l, r)(rnd); }
#define iot ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define timer cerr << 1. * clock() / CLOCKS_PER_SEC << '\n';
#define multi int T; cin >> T; while ( T -- )
template <typename T1, typename T2> T1 min(T1 a, T2 b) { return a < b ? a : b; }
template <typename T1, typename T2> T1 max(T1 a, T2 b) { return a > b ? a : b; }
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define ufile(x)
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
#define Aster(i, s) for (int i = head[s], v; i; i = e[i].nxt)
#define all(s) s.begin(), s.end()
#define eb emplace_back
#define pb pop_back
#define em emplace
const int N = 3e6 + 10;
const int inf = 0x3f3f3f3f;
const ll infll = 0x3f3f3f3f3f3f3f3fll;
// int mod;
const int mod = 3;
// const int mod = 10007;
// const int mod = 469762049, g = 3;
// const int mod = 998244353; // const int g = 3;
// const int mod = 1004535809, g = 3;
// const int mod = 1e9 + 7;
// const int mod = 1e9 + 9;
// const int mod = 1e9 + 3579, bse = 131;
struct FastMod { int m; ll b; void init(int _m) { m = _m; if (m == 0) m = 1; b = ((lll)1 << 64) / m; } FastMod(int _m) { init(_m); }int operator()(ll a) { ll q = ((lll)a * b) >> 64; a -= q * m; if (a >= m) a -= m; return a; } } Mod(mod);
template<const int mod, typename T>
struct z {
T val;
z(T _val = 0) : val(Mod(Mod(_val) + mod)) {}
const T operator * () const { return val; }
T& operator * () { return val; }
inline friend bool operator < (const z & a, const z & b) { return *a < *b; }
inline friend bool operator < (const z & a, const T & b) { return *a < b; }
inline friend bool operator <= (const z & a, const z & b) { return *a <= *b; }
inline friend bool operator <= (const z & a, const T & b) { return *a <= b; }
inline friend bool operator > (const z & a, const z & b) { return *a > *b; }
inline friend bool operator > (const z & a, const T & b) { return *a > b; }
inline friend bool operator >= (const z & a, const z & b) { return *a >= *b; }
inline friend bool operator >= (const z & a, const T & b) { return *a >= b; }
inline friend bool operator == (const z & a, const z & b) { return *a == *b; }
inline friend bool operator == (const z & a, const T & b) { return *a == b; }
inline friend bool operator != (const z & a, const z & b) { return *a != *b; }
inline friend bool operator != (const z & a, const T & b) { return *a != b; }
z& operator = (const z & b) { val = *b; return *this; }
z& operator = (const T & b) { val = b; return *this; }
void operator ++ () { ++ val; } void operator -- () { -- val; }
z operator - () { return z(val == 0 ? val : mod - val); }
friend z operator + (const z & a, const z & b) { z ret(*a + *b); if (*ret >= mod) *ret -= mod; return ret; }
z operator += (const z & b) { val += *b; if (val >= mod) val -= mod; return *this; }
friend z operator - (const z & a, const z & b) { z ret(*a - *b + mod); if (*ret >= mod) *ret -= mod; return ret; }
z operator -= (const z & b) { val -= *b; if (val < 0) val += mod; return *this; }
friend z operator * (const z & a, const z & b) { z ret(Mod(1ll * *a * *b)); return ret; }
z operator *= (const z & b) { val = Mod(1ll * val * *b); return *this; }
friend z operator / (const z & a, const z & b) { z ret(*a / *b); return ret; }
z operator /= (const z & b) { val = val / *b; return *this; }
friend z operator % (const z & a, const z & b) { z ret(*a % *b); return ret; }
z operator %= (const z & b) { val = val % *b; return *this; }
friend z operator + (const z & a, const T & b) { z ret(*a + b); if (*ret >= mod) *ret -= mod; return ret; }
z operator += (const T & b) { val += b; if (val >= mod) val -= mod; return *this; }
friend z operator - (const z & a, const T & b) { z ret(*a - b + mod); if (*ret >= mod) *ret -= mod; return ret; }
z operator -= (const T & b) { val -= b; if (val < 0) val += mod; return *this; }
friend z operator * (const z & a, const T & b) { z ret(Mod(1ll * *a * b)); return ret; }
z operator *= (const T & b) { val = Mod(1ll * val * b); return *this; }
friend z operator / (const z & a, const T & b) { z ret(*a / b); return ret; }
z operator /= (const T & b) { val = val / b; return *this; }
friend z operator % (const z & a, const T & b) { z ret(*a % b); return ret; }
z operator %= (const T & b) { val = val % b; return *this; }
friend istream& operator >> (istream& in, z& b) { in >> *b; return in; }
friend ostream& operator << (ostream& out, z b) { out << *b; return out; }
}; using mint = z<mod, int>;
template <typename T1, typename T2> T1 qp(T1 a, T2 b) { T1 ret = 1; for (; b > 0; a = a * a, b >>= 1) if (b & 1) ret = ret * a; return ret; }
mint fac[N], inv[N], ifac[N];
void init_fac(int bnd, int mask = 0b111) {
if ((mask >> 2) & 1) { fac[0] = fac[1] = 1; rep(i, 2, bnd) fac[i] = fac[i - 1] * i; }
if ((mask >> 1) & 1) { inv[0] = inv[1] = 1; rep(i, 2, bnd) inv[i] = (mod - mod / i) * inv[mod % i]; }
if ((mask >> 0) & 1) { ifac[0] = ifac[1] = 1; if (!((mask >> 1) & 1)) init_fac(bnd, 0b010); rep(i,1,bnd) ifac[i] = ifac[i - 1] * inv[i]; }
}
template <typename T1, typename T2> mint C(T1 n, T2 m) { if (n < m) return 0; return fac[n] * ifac[m] * ifac[n - m]; }
template <typename T1, typename T2> mint invC(T1 n, T2 m) { if (n < m) return 0; return ifac[n] * fac[m] * fac[n - m]; }
const int n = 3, m = 4;
struct mat {
vector<vector<mint>> a;
mat() { a = vector<vector<mint>>(n, vector<mint>(m)); }
int rank() {
int rk = 0;
rep(col, 0, m - 1) {
int t = rk;
while (t < n && a[t][col] == 0)
++t;
if (t == n)
continue;
swap(a[t], a[rk]);
mint inv = qp(a[rk][col], mod - 2);
rep(i, col, m - 1)
a[rk][i] *= inv;
rep(i, 0, n - 1) {
if (i == rk) continue;
mint mul = a[i][col];
rep(j, col, m - 1)
a[i][j] -= a[rk][j] * mul;
}
++rk;
}
return rk;
}
};
using ll = long long;
ll result[1000], cnt[1000];
void dfs(int pos, vector<int>& cur) {
if (pos == n * m) {
mat M;
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
M.a[i][j] = cur[i * m + j];
int k = M.rank();
++ cnt[k];
int countNonZero = 0;
for (int val : cur)
if (val != 0)
countNonZero++;
result[k] += countNonZero;
return;
}
for (int v = 0; v < mod; v++) {
cur[pos] = v;
dfs(pos + 1, cur);
}
}
int main() {
vector<int> arr(n * m, 0);
dfs(0, arr);
rep(k,0,min(n,m)) {
cout << k << ' ';
ll g = __gcd(result[k], cnt[k]);
cout << result[k] / g << "/" << cnt[k] / g << endl;
}
return 0;
}
的部分分:
容易知道答案即
公式由 @H_Kaguya 提供。她说如果她没做出 就不用放 credit,所以题解里没有提到她 /qd
以下是博客签名,与正文无关。
请按如下方式引用此页:
本文作者 joke3579,原文链接:https://www.cnblogs.com/joke3579/p/-/chitchat250305。
遵循 CC BY-NC-SA 4.0 协议。
请读者尽量不要在评论区发布与博客内文完全无关的评论,视情况可能删除。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)