E - Min of Restricted Sum
发现题目给的条件其实非常像图上的边,所以我们考虑按照 \((x, y, z)\) 的方式连无向边。可以发现这个图应该是由很多的联通块组成的,而每个联通块之间是相互独立的。并且对于 \(A_x \oplus A_y = Z\) 这样的式子如果知道了 \(A_x\) 和 \(Z\) 就可以求出 \(A_y\)。因此我们考虑对于每个联通块,二进制拆位后选取联通块中的一个点初始赋值为 \(0\),然后 BFS 求出联通块中的其它值。如果我们发现这样做之后这一位联通块内 \(1\) 的数量更多,我们就将联通块内所有点的这一位 \(\oplus 1\),就取到了这一位的最小值。
const int N = 200010;
int n, m;
int h[N], e[N], ne[N], w[N], idx;
int d[N], vis[N], ans[N];
vector<int> p;void add(int a, int b, int c) {e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
} void bfs(int s) {queue<int> q; q.push(s);p.clear();vis[s] = true; p.push_back(s);while (q.size()) {int u = q.front(); q.pop();for (int i = h[u]; ~i; i = ne[i]) {int v = e[i];if (vis[v] && d[v] != (d[u] ^ w[i])) { puts("-1"); exit(0); }if (vis[v]) continue;d[v] = d[u] ^ w[i]; q.push(v); vis[v] = true;p.push_back(v);}}
} int main() {n = read(), m = read();memset(h, -1, sizeof h);while (m --) {int a = read(), b = read(), c = read();add(a, b, c); add(b, a, c);}rep(i, 1, n) {if (vis[i]) continue;bfs(i);int tmp = 0;rep(k, 0, 30) {int cnt = 0;for (auto x : p)if (d[x] >> k & 1) cnt ++;if (cnt > p.size() - cnt) tmp |= 1 << k;}for (auto x : p) ans[x] = d[x] ^ tmp;}rep(i, 1, n) printf("%d ", ans[i]);return 0;
}
F - Rotated Inversions
考虑一个数如果当前时间 \(k\) 等于 \(M - 1\),那它下一时间会变成 \(0\),就会影响贡献。考虑如果这个数变成 \(0\) 了,那么它的前面和它不相等的所有数都会比它大,就产生逆序对;它后面和它所有不相等的数在它等于 \(M - 1\) 时都比它小,现在都比它大,就会减少逆序对。所以我们只需要先求出 \(k = 0\) 时原序列的逆序对,然后维护一下贡献即可。
const int N = 1000010;
LL n, m;
LL a[N], ans, tr[N], w[N], cnt[N];void add(int x, LL k) {for (; x <= m; x += lowbit(x)) tr[x] += k;
}
LL query(int x) {LL res = 0;for (; x; x -= lowbit(x)) res += tr[x];return res;
}int main() {n = read(), m = read();rep(i, 1, n) a[i] = read();rep(i, 1, n) {w[m - a[i]] += i - 1 - cnt[a[i]];cnt[a[i]] ++; }memset(cnt, 0, sizeof cnt);fro(i, n, 1) {w[m - a[i]] -= n - i - cnt[a[i]];cnt[a[i]] ++;}// rep(i, 0, m - 1) cout << w[i] << ' ';fro(i, n, 1) {a[i] ++;ans += query(a[i] - 1); add(a[i], 1);} rep(k, 0, m - 1) {ans += w[k];printf("%lld\n", ans);}return 0;
}
G - Flip Row or Col
题目的操作相当于每次对一行或一列异或 \(1\)。下文为了方便称 \(n\) 为总行数,\(m\) 为总列数。
首先对于一行来说,如果不考虑列的操作,那么它可以选择整行操作也可以不操作。显然如果这一行有 \(cnt_i\) 个 \(1\),并且 \(cnt_i > m - cnt_i\) ,那么我们肯定操作比不操作优。换句话说,在列操作确定的情况下,行 \(i\) 的最小贡献是 \(\min(cnt_i, m - cnt_i)\)。
观察数据范围可以发现 \(M \leq 18\),显然很可以状压。所以我们把每行给压缩成 \(B_i\),同时所有列上的操作也可以用整数 \(X\) 来表示。此时我们要求 \(\underset{0 \leq X < 2^m}{\min}\{\sum_{i = 1}^{n} \min(\operatorname{popcount}(B_i \oplus X), m - \operatorname{popcount}(B_i \oplus X))\}\),其中 \(\operatorname{popcount}(x)\) 指 \(x\) 的二进制中 \(1\) 的个数。
一个简单的方法是对于每个 \(X\) 都计算一遍,这样做是 \(O(n\times 2^m)\) 的,炸了。
我们考虑优化计算过程,令 \(f_{k, c, x}\) 表示 \(X\) 的值为 \(x\) 时,满足 \(\operatorname{popcount}(B_i \oplus x) = c\) 且 \(0\leq B_i < 2^k\) 的 \(B_i\) 个数。
转移:\(f_{k, c, x} \to f_{k + 1, c, x}\),\(f_{k, c, x \oplus 2 ^ k} \to f_{k + 1, c + 1, x}\),即分为第 \(k + 1\) 位 \(B_i\) 是否等于 \(X\) 两种情况。
那么最终的答案就是 \(\underset{0\leq X < 2^m}{\min}\{\sum_{c = 0}^{m} \min(c, m - c) \times f_{m, c, X}\}\)。
这么做的时间复杂度是 \(O(m^2 \times 2^m)\),注意到 DP 的第一维可以优化掉,所以空间是 \(O(m\times 2^m)\)。
const int N = 200010, M = 20;
int n, m;
LL f[M][1 << M], b[N];int main() {n = read(), m = read();rep(i, 1, n) rep(j, 1, m) {char c; scanf(" %c", &c);if (c == '1') b[i] |= (1 << m - j);} rep(i, 1, n) f[0][b[i]] ++;rep(i, 0, m - 1)fro(j, i, 0)rep(k, 0, (1 << m) - 1)f[j + 1][k] += f[j][k ^ (1 << i)];LL ans = 1e18;rep(k, 0, (1 << m) - 1) {LL res = 0;rep(i, 0, m) res += min(i, m - i) * f[i][k];ans = min(ans, res);}printf("%lld\n", ans);return 0;
}