专栏文章
AT ARC184E Accumulating Many Times
AT_arc184_e题解参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @mio2fa6q
- 此快照首次捕获于
- 2025/12/02 12:15 3 个月前
- 此快照最后确认于
- 2025/12/02 12:15 3 个月前
因为 并不具有什么性质,所以尝试从操作入手。
尝试形式化的刻画这个操作:
记 ,进行操作就是乘上 。
不过这样形式太难看,考虑转化一下,转成由 变为 ,前缀和对应就被转成差分,就对应 。
不过这样形式太难看,考虑转化一下,转成由 变为 ,前缀和对应就被转成差分,就对应 。
继续考虑叠加操作,当进行 次操作后,可以表示为 。
此时这个 有很好的性质,具体来说考虑 Lucas 定理:对于质数 ,有 。
那么取 ,发现可以表示为二进制拆分的形式,即令 ,那么 。
那么取 ,发现可以表示为二进制拆分的形式,即令 ,那么 。
经过这些推导,能够发现这个转移一定是一个环,具体来说考虑若取 ,那么 在只保留前 位的情况下还会是 。
那么此时 就有了计算的办法:找到 对应的环,再根据在环上的具体位置计算操作次数。
此时要如何找到其对应的环呢?
这就相当于是把一个环内的元素看做一个等价类,对于这个问题,我们有很好的办法——找到代表元。
不妨认为一个环的代表元是环中字典序最小的数组。
这就相当于是把一个环内的元素看做一个等价类,对于这个问题,我们有很好的办法——找到代表元。
不妨认为一个环的代表元是环中字典序最小的数组。
那么如何找到这个代表元?
前文已经提供了一定的思路,考虑对操作到代表元的操作次数 进行二进制拆分,转而对于每个 考虑是否要选上。
前文已经提供了一定的思路,考虑对操作到代表元的操作次数 进行二进制拆分,转而对于每个 考虑是否要选上。
考虑到前缀的 肯定不管如何操作都不会改变,记最靠前的 在 下标,考虑如何最小化字典序。
那么接下来就需要最小化 下标的值,所以若 ,乘上 ,就可以让 变为 ;同样的,也可以操作使 变为 。
但是对于 ,因为 都已经被考虑过了,如果为了 变为 就会导致 或是 变成 ,这明显是不优的。
于是可以知道这个过程就是依次考虑 ,若 为 ,就乘上 使其变为 。
那么接下来就需要最小化 下标的值,所以若 ,乘上 ,就可以让 变为 ;同样的,也可以操作使 变为 。
但是对于 ,因为 都已经被考虑过了,如果为了 变为 就会导致 或是 变成 ,这明显是不优的。
于是可以知道这个过程就是依次考虑 ,若 为 ,就乘上 使其变为 。
当 时明显是没有用的,所以整个过程只会经过 次。
并且这也同时求出了 变到环内代表元的操作次数,可以利用这个信息求解答案。
不过求解答案时还有个问题,可能从 到 要跨过代表元,此时这个计算式就与环长有关,环长一定是最小的 吗?
考虑 ,其周期其实就为 而不为 ,那显然是错的。
回到刚刚求解代表元的过程,当 时没有用就是因为此时操作 次一定是相同的结果。
而对于满足该条件最小的 ,因为 ,所以操作 一定对应着不同的结果,那么通过二进制组合起来,操作 次得到的结果都不同。
所以可以知道周期就为满足 的最小的 ,特别的,全 序列的周期为 。
考虑 ,其周期其实就为 而不为 ,那显然是错的。
回到刚刚求解代表元的过程,当 时没有用就是因为此时操作 次一定是相同的结果。
而对于满足该条件最小的 ,因为 ,所以操作 一定对应着不同的结果,那么通过二进制组合起来,操作 次得到的结果都不同。
所以可以知道周期就为满足 的最小的 ,特别的,全 序列的周期为 。
那么对于周期长度为 ,分别操作 次能够操作成代表元的两个序列 ,可以推出 的操作次数即为 。
于是使用树状数组统计 的值即可。
于是使用树状数组统计 的值即可。
时间复杂度 。
带 的原因是我是直接根据代表元排序进行的划分等价类。
代码中序列的下标是 。
CPP#include <bits/stdc++.h>
using ll = long long;
constexpr ll mod = 998244353;
constexpr int maxn = 1e6 + 10;
int n, m;
std::vector<int> a[maxn];
int dis[maxn];
int od[maxn];
struct fenwick {
ll sum[maxn * 2];
inline void add(int x, ll y) {
for (; x < maxn * 2; x += x & -x) {
sum[x] = (sum[x] + y) % mod;
}
}
inline ll query(int x) {
ll y = 0;
for (; x >= 1; x -= x & -x) {
y = (y + sum[x]) % mod;
}
return y;
}
} f, g;
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
a[i].resize(m);
for (int &x : a[i]) {
scanf("%d", &x);
}
int p = 0;
while (p < m && ! a[i][p]) {
p++;
}
for (int l = 1; p + l < m; l *= 2) {
if (a[i][p + l]) {
for (int j = m - 1; j >= p + l; j--) {
a[i][j] ^= a[i][j - l];
}
dis[i] += l;
}
}
}
std::iota(od + 1, od + n + 1, 1);
std::sort(od + 1, od + n + 1, [&](int x, int y) {
return a[x] == a[y] ? x < y : a[x] < a[y];
});
ll ans = 0;
for (int l = 1, r = 1; l <= n; l = ++r) {
while (r + 1 <= n && a[od[l]] == a[od[r + 1]]) {
r++;
}
int p = 0;
while (p < m && ! a[od[l]][p]) {
p++;
}
int M = 1;
while (p + M < m) {
M *= 2;
}
ll cnt = 0, sum = 0;
for (int i = l; i <= r; i++) {
const int u = od[i];
const ll c0 = f.query(dis[u] + 1);
ans = (ans + cnt * dis[u] - sum + (cnt - c0) * M + mod) % mod;
f.add(dis[u] + 1, 1);
cnt++, sum += dis[u];
}
for (int i = l; i <= r; i++) {
const int u = od[i];
f.add(dis[u] + 1, mod - 1);
}
}
printf("%lld\n", ans);
return 0;
}
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...