zoukankan      html  css  js  c++  java
  • 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的烦恼。(很显然,我不会写,要不就不讲上述的方法了……)

  • 相关阅读:
    idea 中使用 svn
    [剑指offer] 40. 数组中只出现一次的数字
    [剑指offer] 39. 平衡二叉树
    [剑指offer] 38. 二叉树的深度
    [剑指offer] 37. 数字在排序数组中出现的次数
    [剑指offer] 36. 两个链表的第一个公共结点
    [剑指offer] 35. 数组中的逆序对
    vscode在win10 / linux下的.vscode文件夹的配置 (c++/c)
    [剑指offer] 34. 第一个只出现一次的字符
    [剑指offer] 33. 丑数
  • 原文地址:https://www.cnblogs.com/mrclr/p/9409858.html
Copyright © 2011-2022 走看看