机房测试1:string(线段树)

题目:

分析:

暴力:每一次对区间暴力排序。

优化:如果可以知道一个区间中有哪种字符,这些字符分别有多少个,就可以直接按字典序枚举,将它们快速地插入区间中了。

题中有一个重要信息:只有小写字母,即只有26种字符。

第一种方法:

可以用一个线段树来维护,每个节点储存26个字符在这个区间中的对应情况。每次修改,就query统计出所求区间每一种字符的个数。

然后再区间修改,具体见代码。

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define N 100005
#define ri register int
int num[26][N*4],now[N*4],a[N],fl[N*4];
void update(int s) { for(ri i=0;i<26;++i) num[i][s]=num[i][s<<1]+num[i][s<<1|1]; }
void build(int s,int l,int r)
{
    fl[s]=-1;
    if(l==r){ num[a[l]][s]=1; return ; }//一维存颜色,一维存线段树节点
    build(s<<1,l,mid); build(s<<1|1,mid+1,r);
    update(s);
}
void pushdown(int s,int l,int r)
{
    if(fl[s]==-1) return ;
    int tmp=mid-l+1;
    if(fl[s]==1){//标记下传 就是将前半部分的字符按升序传给左区间 后半部分给右区间
        int cnt=0,p=-1;
        while(cnt<tmp) p++,cnt+=num[p][s],num[p][s<<1]=num[p][s],num[p][s<<1|1]=0;
        cnt-=tmp;
        num[p][s<<1]-=cnt; num[p][s<<1|1]+=cnt;
        for(p++;p<26;p++) num[p][s<<1|1]=num[p][s],num[p][s<<1]=0;
    }
    else{
        int cnt=0,p=26;
        while(cnt<tmp) p--,cnt+=num[p][s],num[p][s<<1]=num[p][s],num[p][s<<1|1]=0;
        cnt-=tmp;
        num[p][s<<1]-=cnt; num[p][s<<1|1]+=cnt;
        for(p--;p>=0;p--) num[p][s<<1|1]=num[p][s],num[p][s<<1]=0;
    }
    fl[s<<1]=fl[s]; fl[s<<1|1]=fl[s]; fl[s]=-1;
}
void query(int s,int l,int r,int L,int R)
{
    if(L<=l && r<=R){
        for(ri i=0;i<26;++i) now[i]+=num[i][s]; return ;
    }
    pushdown(s,l,r);
    if(L<=mid) query(s<<1,l,mid,L,R);
    if(R>mid)  query(s<<1|1,mid+1,r,L,R);
}
void modify(int s,int l,int r,int L,int R,int op)
{
    if(l==r){
        int cnt=0,tmp=l-L+1,p;
        if(op){
            p=-1;
            while(cnt<tmp) p++,cnt+=now[p];
        }
        else{
            p=26;
            while(cnt<tmp) p--,cnt+=now[p];
        }
        for(ri i=0;i<26;++i) num[i][s]=0;
        num[p][s]=1;
        return ;
    }//我觉得这可能不需要
    if(L<=l && r<=R){
        fl[s]=op;
        int cnt=0,tmp1=l-L+1,tmp2=r-L+1;//画线段理解
        for(ri i=0;i<26;++i) num[i][s]=0;
        if(op){//升序
            int p=-1;
            while(cnt<tmp1) p++,cnt+=now[p];//先从L跳到 l,用升序的字符跳位置
            num[p][s]=cnt-tmp1+1;//走出去了一段 要减掉
            while(cnt<tmp2) p++,num[p][s]=now[p],cnt+=now[p];//从l到 r,依次按升序修改此区间的字符种类
            num[p][s]-=cnt-tmp2;//同上,减去跳出去的一段
        }
        else{
            int p=26;
            while(cnt<tmp1) p--,cnt+=now[p];
            num[p][s]=cnt-tmp1+1;
            while(cnt<tmp2) p--,num[p][s]=now[p],cnt+=now[p];
            num[p][s]-=cnt-tmp2;
        }
        return ;
    }
    pushdown(s,l,r);
    if(L<=mid) modify(s<<1,l,mid,L,R,op);
    if(R>mid)  modify(s<<1|1,mid+1,r,L,R,op);
    update(s);
}
void print(int s,int l,int r)
{
    if(l==r){//递归到叶子节点输出
        for(ri i=0;i<26;++i) if(num[i][s]) { printf("%c",i+‘a‘); break; }
        return ;
    }
    pushdown(s,l,r);
    print(s<<1,l,mid); print(s<<1|1,mid+1,r);
}
char s[N];
int main()
{
    freopen("string.in","r",stdin);
    freopen("string.out","w",stdout);
    int n,m,l,r,op;
    scanf("%d%d",&n,&m);
    scanf("%s",s);
    for(ri i=1;i<=n;++i) a[i]=s[i-1]-‘a‘;
    build(1,1,n);
    while(m--){
        scanf("%d%d%d",&l,&r,&op);
        for(ri i=0;i<26;++i) now[i]=0;
        query(1,1,n,l,r);//统计区间中每种字符有多少个
        modify(1,1,n,l,r,op);
    }
    print(1,1,n);
}
/*
5 2
cabcd
1 3 1
3 5 0

5
2
trlyo
1 4 1
1 1 0
*/

1

第二种方法:

同样是线段树,但每个节点维护的是这段区间都是哪一种字符,如果这段区间有不同的字符,即为0。

修改时还是要区间求和,然后按升序(或降序)for每一种字符,如果这种字符有的话,就将对应的位置修改成它。修改时递归到整块都是同种的就暴力修改,然后打标记。pushdown时更简单。

相较于第一种,第二种方法更好掌握。

2

原文地址:https://www.cnblogs.com/mowanying/p/11604603.html

时间: 2024-10-10 14:36:21

机房测试1:string(线段树)的相关文章

string [线段树优化桶排]

题意大概是给你一个字符串,1e5次修改,每次给一个区间升序排列或降序排列,最后输出这个字符串; 其实是个挺裸的线段树优化题;但是我没有意识去结合桶排,扑该..... 首先 1.40分算法 O(NMlogN) 1 inline void update(int k){ 2 for(int i=1;i<=26;++i){ 3 tmp[k].tong[i]=tmp[ls].tong[i]+tmp[rs].tong[i]; 4 } 5 } 6 7 void ud(int k,int l,int r,int

[CSP-S模拟测试]:Permutation(线段树+拓扑排序+贪心)

题目描述 你有一个长度为$n$的排列$P$与一个正整数$K$你可以进行如下操作若干次使得排列的字典序尽量小对于两个满足$|i−j|\geqslant K$且$|P_i−P_j|=1$的下标$i$与$j$,交换$P_i$与$P_j$ 输入格式 第一行包括两个正整数$n$与$K$第二行包括$n$个正整数,第$i$个正整数表示$P_i$ 输出格式 输出一个新排列表示答案输出共$n$行,第$i$行表示$P_i$ 样例 样例输入: 8 34 5 7 8 3 1 2 6 样例输出: 12675348 数据范

【10.10校内测试】【线段树维护第k小+删除】【lca+主席树维护前驱后驱】

贪心思想.将a排序后,对于每一个a,找到对应的删除m个后最小的b,每次更新答案即可. 如何删除才是合法并且最优的?首先,对于排了序的a,第$i$个那么之前就应该删除前$i-1$个a对应的b.剩下$m-i+1$可以删,那么在剩下的b中查找第$m-i+2$小即可.每次做完就删除当前a对应的b. 注意离散化. 还有数组不要开大了.... #include<bits/stdc++.h> #define LL long long using namespace std; void read(int &a

POJ 2887 Big String 线段树 离线处理

一开始看的时候没什么思路,后来一看卧槽不是简单的离线处理么.反着插入一遍然后直接查询就好了. #include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define lson rt << 1, l, mid #define rson rt << 1 | 1, mid + 1, r const int maxq = 2e3 + 10; const

uva 11992 线段树对矩阵进行更新查询

http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=3143 把矩阵变成一行,然后计算位置,lrj给了线段树数组做法 但是我做的线段树空间过大,直接爆掉,所以换方法了 主要还是测试自己的线段树区间更新的模板 各种RE+WA之后AC,,,,, 做的时候出现的几个错误: 1.行和列弄错 2.build初始化的时候,mmin mmax 都初始化为0才对

机房测试5:reverse(bfs+set 或 线段树优化建图)

题目: 分析: 首先画样例分析一下,会发现如果要求一个位置要多少次翻转,就将这个位置向与它关联的点连边(关联点指的是可能与它值互换的位置),一直连到起点为止,连边的次数即为它所需步数. 所以转换成求单源最短路,因为边权为1,可以用bfs. 但是这道题n的范围很大,刚刚的做法是n*k的,考虑优化. 法1:在建图上优化 题目要求的是区间翻转,所以也对应着相关性质:每个点连边一定是都连的奇数点或偶数点(画图可知),且这些奇数偶数点都对应着一段连续的区间. 如果可以将点向点连边优化成点向区间连边,复杂度

HDU5008 Boring String Problem(后缀数组 + 二分 + 线段树)

题目 Source http://acm.hdu.edu.cn/showproblem.php?pid=5008 Description In this problem, you are given a string s and q queries. For each query, you should answer that when all distinct substrings of string s were sorted lexicographically, which one is

HDU - 3973 AC&#39;s String(Hash+线段树)

http://acm.hdu.edu.cn/showproblem.php?pid=3973 题意 给一个词典和一个主串.有两种操作,查询主串某个区间,问这主串区间中包含多少词典中的词语.修改主串某一位置的字符. 分析 题目要求区间查询,和单点更新,那么最先想到的应该是线段树.可字符串怎么利用线段树呢?用hash!首先将词典按规则hash后放入map,然后将主串的hash值放置入线段树中.问题来了,怎么更新结点的hash值呢?假如现在我们知道了s[l...mid]和s[mid+1...r]的ha

[题解]线段树专题测试2017.1.21

很单纯的一道线段树题.稍微改一下pushDown()就行了. Code(线段树模板竟然没超100行) 1 #include<iostream> 2 #include<sstream> 3 #include<cstdio> 4 #include<cmath> 5 #include<cstdlib> 6 #include<cstring> 7 #include<cctype> 8 #include<queue> 9