CF558E A Simple Task
Posted mrclr
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CF558E A Simple Task相关的知识,希望对你有一定的参考价值。
题目大意: 给定一个长度不超过10^5的字符串(小写英文字母),和不超过5000个操作。
每个操作 L R K 表示给区间[L,R]的字符串排序,K=1为升序,K=0为降序。
最后输出最终的字符串
首先这么想想,对于一段区间的排序,排完序的样子和排序之前每个字母的位置并没有关系,而是和每一个字母出现的次数有关。所以我们对于每一次操作,统计出区间中每一个字母出现了多少次,然后按字典序排序就行。更确切的说,就是这个区间中的哪一个部分都改成某一个字母,区间修改。
既然是区间修改,那么就可以用线段树实现。不过这样的话,打lazy标记就显得不是很方便。为此,我们可以开26个线段树,每一个字母开一个长度为n的权值线段树,如果第i为是这个字母,我们就把这一位改成1,然后统计区间中这个字母有多少个,就相当于求区间和了。至于修改,那就是将这个字母所在线段树的区间全都改成1.然后把操作区间的别的地方改成0即可。
举个栗子:
acbcaab
然后将[1, 6]按升序排序。
那么我们首先分别在a, b, c所在的线段树上查到了[1, 6]的区间和,即统计出了每个字母的出现次数。
然后排序的时候,对于a所在的线段树,我们将[1, 3]都改成了1,[4, 6]改成了0;对于b所在线段树,我们将[4, 4]改成了1,[1, 3]和[5, 6]改成了0;对于c,我们将[5, 6]改成了1,[1, 4]改成了0.
这样这个区间就排完序了。
那么怎么输出最终答案呢?
只要对于每一位,暴力的从0到26循环,看哪个字母在这一位上是1,就说明这一位是这个字母了。
配合break,时间复杂度最坏为O(nlogn * 26)
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #include<cstdlib> 7 #include<cctype> 8 #include<vector> 9 #include<stack> 10 #include<queue> 11 using namespace std; 12 #define enter printf(" ") 13 #define space printf(" ") 14 #define Mem(a) memset(a, 0, sizeof(a)) 15 typedef long long ll; 16 typedef double db; 17 const int INF = 0x3f3f3f3f; 18 const db eps = 1e-8; 19 const int maxn = 2e7 + 5; 20 inline int read() 21 { 22 int ans = 0; 23 char ch = getchar(), last = ‘ ‘; 24 while(!isdigit(ch)) {last = ch; ch = getchar();} 25 while(isdigit(ch)) 26 { 27 ans = ans * 10 + ch - ‘0‘; ch = getchar(); 28 } 29 if(last == ‘-‘) ans = -ans; 30 return ans; 31 } 32 inline void write(ll x) 33 { 34 if(x < 0) x = -x, putchar(‘-‘); 35 if(x >= 10) write(x / 10); 36 putchar(‘0‘ + x % 10); 37 } 38 39 int n, q; 40 char s[100005]; 41 42 int cnt = 0, root[30], lson[maxn], rson[maxn], l[maxn], r[maxn]; 43 //lson[now]和rson[now]分别记录now的左右儿子的编号,代替了now << 1和 now <<1 | 1 44 int sum[maxn], lazy[maxn]; 45 void build(int& now, int L, int R) //我这个写法是先吧所有点开好了,不是动态开点(竟然比某位大佬的动态开点快) 46 { 47 now = ++cnt; lazy[now] = -1; 48 l[now] = L; r[now] = R; 49 if(L == R) return; 50 int mid = (L + R) >> 1; 51 build(lson[now], L, mid); 52 build(rson[now], mid + 1, R); 53 } 54 void add(int now, int id) 55 { 56 if(l[now] == r[now]) {sum[now]++; return;} 57 int mid = (l[now] + r[now]) >> 1; 58 if(id <= mid) add(lson[now], id); 59 else add(rson[now], id); 60 sum[now] = sum[lson[now]] + sum[rson[now]]; 61 } 62 void pushdown(int now) 63 { 64 if(lazy[now] != -1) //因为lazy[now]=0代表将区间都改为0,所以没有标记要换一个记号 65 { 66 sum[lson[now]] = (r[lson[now]] - l[lson[now]] + 1) * lazy[now]; 67 sum[rson[now]] = (r[rson[now]] - l[rson[now]] + 1) * lazy[now]; 68 lazy[lson[now]] = lazy[now]; 69 lazy[rson[now]] = lazy[now]; 70 lazy[now] = -1; 71 } 72 73 } 74 void update(int now, int L, int R, int d) 75 { 76 if(L == l[now] && R == r[now]) 77 { 78 sum[now] = (r[now] - l[now] + 1) * d; 79 lazy[now] = d; return; 80 } 81 pushdown(now); 82 int mid = (l[now] + r[now]) >> 1; 83 if(R <= mid) update(lson[now], L, R, d); 84 else if(L > mid) update(rson[now], L, R, d); 85 else {update(lson[now], L, mid, d); update(rson[now], mid + 1, R, d);} 86 sum[now] = sum[lson[now]] + sum[rson[now]]; 87 } 88 int query(int now, int L, int R) 89 { 90 if(!sum[now]) return 0; //优化 91 if(L == l[now] && R == r[now]) return sum[now]; 92 pushdown(now); 93 int mid = (l[now] + r[now]) >> 1; 94 if(R <= mid) return query(lson[now], L, R); 95 else if(L > mid) return query(rson[now], L, R); 96 else return query(lson[now], L, mid) + query(rson[now], mid + 1, R); 97 } 98 99 int main() 100 { 101 n = read(); q = read(); 102 scanf("%s", s + 1); 103 for(int i = 0; i < 26; ++i) build(root[i], 1, n); 104 for(int i = 1; i <= n; ++i) add(root[s[i] - ‘a‘], i); 105 for(int i = 1; i <= q; ++i) 106 { 107 int L = read(), R = read(), k = read(); 108 if(k) 109 { 110 int pre = L - 1; 111 for(int j = 0; j < 26; ++j) //枚举每一棵线段树 112 { 113 int ssum = query(root[j], L, R); 114 if(ssum) 115 { 116 update(root[j],L,R,0); //先都改成0,在局部覆盖1 117 update(root[j], pre + 1, pre + ssum, 1); 118 } 119 pre += ssum; 120 } 121 } 122 else 123 { 124 int pre = L - 1; 125 for(int j = 25; j >= 0; --j) //降序,就倒着枚举 126 { 127 int ssum = query(root[j], L, R); 128 if(ssum) 129 { 130 update(root[j],L,R,0); 131 update(root[j], pre + 1, pre + ssum, 1); 132 } 133 pre += ssum; 134 } 135 } 136 } 137 for(int i = 1; i <= n; ++i) //很暴力的查询 138 for(int j = 0; j < 26; ++j) 139 if(query(root[j], i, i)) {printf("%c", ‘a‘ + j); break;} 140 enter; 141 return 0; 142 }
这道题时限5秒,然而还特别容易TLE,所以得做好常数优化工作。
据说某位大佬线段树上每个节点记录26个字母出现的情况,所以只开了一棵线段树,自然就十分的快了,毫无TLE的烦恼。(很显然,我不会写,要不就不讲上述的方法了……)
以上是关于CF558E A Simple Task的主要内容,如果未能解决你的问题,请参考以下文章