AT ARC184E Accumulating Many Times
cnblogs。
因为
尝试形式化的刻画这个操作:
记
不过这样形式太难看,考虑转化一下,转成由
继续考虑叠加操作,当进行
此时这个
那么取
经过这些推导,能够发现这个转移一定是一个环,具体来说考虑若取
那么此时
此时要如何找到其对应的环呢?
这就相当于是把一个环内的元素看做一个等价类,对于这个问题,我们有很好的办法——找到代表元。
不妨认为一个环的代表元是环中字典序最小的数组。
那么如何找到这个代表元?
前文已经提供了一定的思路,考虑对操作到代表元的操作次数
考虑到前缀的
那么接下来就需要最小化
但是对于
于是可以知道这个过程就是依次考虑
当
并且这也同时求出了
不过求解答案时还有个问题,可能从
考虑
回到刚刚求解代表元的过程,当
而对于满足该条件最小的
所以可以知道周期就为满足
那么对于周期长度为
于是使用树状数组统计
时间复杂度
带
代码中序列的下标是
#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;
}