ABC408 D~F 题解
D
题面
给定一个长度为 \(n\) 的由 01 组成的字符串 \(S\),每次操作可以将某个 0 改成 1,或者将某个 1 改成 0 。
求字符串中至多有一个连续 1 串的最小操作次数。
题解
解法1
考场思路,将每个连续 1 串挑出来,考虑每个 1 串选或者不选。
设 \(f(i,0/1)\) 表示第 \(i\) 个 1 串选/不选的最小操作次数,分别考虑如何转移。
如果不选,那么不需要将两个连续 1 串中间的 0 变为 1,所以新增操作次数即为当前 1 串的长度。
如果选,那么要和前面最后一个 1 串连起来,或者自己成为第一个 1 串。
有转移:
其中 \(h,t\) 表示一个 1 串的头、尾坐标,\(sum_i\) 表示前 \(i\) 个 1 串的长度和。
解法2
设 \(sum_i\) 表示前 \(i\) 个位置中 1 的个数。
假设我们枚举了最后 1 串的左右端点 \(l,r\) ,那么答案应该怎么算?
不难得出为 \(2sum_{l - 1} - l + r- 2sum_r + sum_n + 1\)。
设 \(a_i = 2sum_{i - 1} - i, b_i = i - 2sum_i + sum_n + 1\),那么刚才的答案也就能够写成 \(a_l + b_r\) 。
假设我们固定了一个右端点,我们也就是要找到 \(a_l\) 最小的 \(l\) ,从而用 \(a_l + b_r\) 来更新答案。
我们完全可以用一个变量来维护最小 \(a_l\) ,然后右端点不断向右扫。
code
解法1
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>using namespace std;namespace michaele {const int N = 2e5 + 10;int n, cnt;int f[N][2], sum[N], st[N], top;char s[N];struct seg {int h, t, len;} a[N];void solve () {cnt = 0;cin >> n;scanf ("%s", s + 1);int p = 1;while (p <= n) {if (s[p] == '0') {p ++;continue;}int h = p;while (s[p] == '1' && p <= n) p ++;a[ ++ cnt] = {h, p - 1, p - h};sum[cnt] = sum[cnt - 1] + p - h;}if (cnt == 0) {cout << 0 << endl;return;}memset (f, 0x3f, sizeof f);f[1][0] = a[1].len;f[1][1] = 0;st[1] = 1, top = 1;for (int i = 2; i <= cnt; i ++) {f[i][0] = min (f[i - 1][0], f[i - 1][1]) + a[i].len;int j = st[top];f[i][1] = min (f[j][1] + a[i].h - a[j].t - 1, sum[i - 1]);while (top && f[i][1] - a[i].t <= f[st[top]][1] - a[st[top]].t) top --;st[ ++ top] = i;}cout << min (f[cnt][0], f[cnt][1]) << endl;}void Main () {int T;cin >> T;while (T --) {solve ();}}
}int main () {// freopen ("test/test.in", "r", stdin);// freopen ("test/test.out", "w", stdout);michaele :: Main ();return 0;
}
解法2
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <deque>using namespace std;namespace michaele {const int N = 2e5 + 10, M = N << 1;int n, sum[N], a[N], b[N];char s[N];void solve () {cin >> n;scanf ("%s", s + 1);for (int i = 1; i <= n; i ++) {sum[i] = sum[i - 1] + (s[i] == '1');}if (sum[n] == 0) {cout << 0 << endl;return;}for (int i = 1; i <= n; i ++) {a[i] = 2 * sum[i - 1] - i;b[i] = i - 2 * sum[i] + 1 + sum[n];}int ans = 2e9, mn = 2e9;for (int i = 1; i <= n; i ++) {mn = min (mn, a[i]);ans = min (ans, mn + b[i]);}cout << ans << endl;}void Main () {int T;cin >> T;while (T --) {solve ();}}
}int main () {michaele :: Main ();return 0;
}
E
题面
给定一张 \(n\) 个点,\(m\) 条边的无向连通图,每条边有边权 \(w_i\)
定义某条路径的长度为路径上的边权的异或和,求从 1 到 n 的最短路径长度。
\(2 \le n \le 2 \times 10^5, n - 1 \le m \le 2 \times 10^5\)
\(0 \le w_i \le 2^{30}\)
题解
这种位运算的题,一般来说都要将每一位分开考虑。
这里我们是要求一个从 1 到 n 的最短或路径,我们从高到低考虑每一位。
如果这一位能选 0,那么一定选 0,否则只能选 1。
如何判断这一位能不能选 0 ?
我们可以将边权变为这一位的值,然后跑个 01BFS,看最后 \(dis_n\) 是否为 0 即可。如果这一位能选 0 ,那么要将所有这一位为 1 的边删除,保证后面的选择是在这一位为 0 的前提下的。
时间复杂度 \(O(n \log n)\)
code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <deque>using namespace std;namespace michaele {const int N = 2e5 + 10, M = N << 1;int n, m;int h[N], ver[M], ne[M], e[M], tot;bool del[M];void add (int x, int y, int z) {ver[ ++ tot] = y;ne[tot] = h[x];h[x] = tot;e[tot] = z;}int dis[N];void bfs (int bit) {memset (dis, 0x3f, sizeof dis);deque <int> q;dis[1] = 0;q.push_front (1);while (q.size ()) {int x = q.front ();q.pop_front ();for (int i = h[x]; i; i = ne[i]) {if (del[i]) continue;int y = ver[i], z = (e[i] >> bit) & 1;if (dis[y] > (dis[x] | z)) {dis[y] = (dis[x] | z);if (z == 0) q.push_front (y);else q.push_back (y);}}}}void solve () {cin >> n >> m;for (int i = 1; i <= m; i ++) {int x, y, z;cin >> x >> y >> z;add (x, y, z);add (y, x, z);}int ans = 0;for (int i = 30; i >= 0; i --) {bfs (i);if (dis[n] != 0) ans += (1 << i);else {for (int j = 1; j <= tot; j ++) {if ((e[j] >> i) & 1) {del[j] = 1;}}}}cout << ans << endl;}
}int main () {michaele :: solve ();return 0;
}
F
题面
有 \(n\) 个木桩,第 \(i\) 个木桩的高度为 \(H_i\)。
小乐要做个游戏。他将任意选一个木桩为起点,然后不断移动到下一个木桩,从木桩 \(i\) 可以移动到木桩 \(j\) 当且仅当 \(H_j \le H_i - D \and|i - j| \le R\)。
求他在游戏中最多可以移动多少次。
\(1 \le D, R \le n \le 5 \times 10^5\)
\(H\) 为 \(1 \sim n\) 的排列
题解
这道题转移有两个限制,应该算个 trick。
如果单个限制,很好做,第一个维护一个 \(set\),第二个维护线段树。
但这两个合到一起,我们可以从小到大枚举 \(H_i\),然后将 \(H_i - D\) 对应位置的 \(f\) 单点修改到线段树中,然后每次在线段树上查询对应区间的最大值更新 \(H_i\) 对应位置的 \(f\) 即可。
时间复杂度 \(O(n \log n)\)
code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <deque>using namespace std;namespace michaele {#define ls (p << 1)#define rs (p << 1 | 1)const int N = 5e5 + 10, M = N << 1;int n, D, R;int a[N], id[N], f[N];int val[N << 2];void update (int p) {val[p] = max (val[ls], val[rs]);}void modify (int p, int l, int r, int pos, int d) {if (l == r) {val[p] = d;return;}int mid = (l + r) >> 1;if (pos <= mid) modify (ls, l, mid, pos, d);else modify (rs, mid + 1, r, pos, d);update (p);}int query (int p, int l, int r, int x, int y) {if (x <= l && r <= y) {return val[p];}int mid = (l + r) >> 1;int res = -1;if (x <= mid) res = max (res, query (ls, l, mid, x, y));if (mid < y) res = max (res, query (rs, mid + 1, r, x, y));return res; }void solve () {cin >> n >> D >> R;for (int i = 1; i <= n; i ++) {cin >> a[i];id[a[i]] = i;}memset (val, -1, sizeof val);int ans = 0;for (int i = 1; i <= n; i ++) {int pos = id[i];if (i > D) {modify (1, 1, n, id[i - D], f[id[i - D]]);int tmp = query (1, 1, n, pos - R, pos + R); if (tmp == -1) f[pos] = 0;else f[pos] = tmp + 1;} else {f[pos] = 0;}ans = max (ans, f[pos]);}cout << ans << endl;}
}int main () {michaele :: solve ();return 0;
}