题目描述:
这题改编自CF217E,给出一串字符串,有n个操作,将区间[l, r]的字符中编号为奇数的写下,再把偶数的写在后面,然后把它们插入r和r + 1之间,问最后字符串的前k位。
解题思路:
因为前面的操作不会对后面的操作有影响,所以我们考虑把操作倒过来做。相应的,一开始就只有k个位置有效,然后对于操作[l, r],就把[r + 1, r + (r - l + 1)]标记为已操作,并记录每一位是哪一位转移过来的。然后对于前一个操作,就只能做那些未标记的位置。相当于对未标记的位置重标号,然后像刚刚一样继续做。当然,如果操作位置超出了当前未标记的位置,就不用做了。所以这样最多只要做k次。具体要怎么记录位置呢,可以用线段树,类似二分的找。在oj上被卡了线段树做法,所以改成非递归版卡过去了。
代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 using namespace std; 5 6 const int N = 3e6 + 10; 7 int k, n, l[N], r[N], tr[N * 4], f[N]; 8 char s[N], ans[N]; 9 10 void build(int o, int l, int r) { 11 tr[o] = r - l + 1; 12 if (l == r) return; 13 int m = l + r >> 1; 14 build(o << 1, l, m); 15 build(o << 1 | 1, m + 1, r); 16 } 17 18 int find(int o, int l, int r, int k, int p) { 19 while (l != r) { 20 tr[o] -= p; 21 int m = l + r >> 1; 22 if (tr[o << 1] >= k) r = m, o <<= 1; 23 else k -= tr[o << 1], o = o << 1 | 1, l = m + 1; 24 } 25 tr[o] -= p; 26 return l; 27 } 28 29 void work() { 30 for (int i = n; i >= 1; i --) { 31 if (r[i] >= tr[1]) continue; 32 int t = l[i] & 1 ^ 1; 33 int now = l[i] + t; 34 if (now > r[i]) now = l[i] + (t ^ 1); 35 for (int j = 1; j <= r[i] - l[i] + 1 && r[i] < tr[1]; j ++) { 36 int p = find(1, 1, k, r[i] + 1, 1); 37 f[p] = find(1, 1, k, now, 0); 38 now += 2; 39 if (now > r[i]) now = l[i] + (t ^ 1); 40 } 41 } 42 for (int i = 1, j = 1; i <= k; i ++) 43 if (f[i]) ans[i] = ans[f[i]]; else ans[i] = s[j ++]; 44 puts(ans + 1); 45 } 46 47 int main() { 48 gets(s + 1); 49 scanf("%d %d", &k, &n); 50 build(1, 1, k); 51 for (int i = 1; i <= n; i ++) scanf("%d %d", &l[i], &r[i]); 52 work(); 53 return 0; 54 }
时间: 2024-10-10 20:30:42