YBTOJ 字符串排序
这道题卡了快一天QAQ
最开始照着书上的提示写了一个程序,死活T40,然后我找到CF588E原题交了一遍AC了,就以为是评测机的问题,于是加上了各种卡常优化如火车头 ,虽然多过了一个点到了50分但还是差7msAC。
为了纪念我把50分TLE代码放一下:
#pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native")
#include <immintrin.h>
#pragma GCC optimize(2)
%:pragma GCC optimize(3)
%:pragma GCC optimize("Ofast")
%:pragma GCC optimize("inline")
%:pragma GCC optimize("-fgcse")
%:pragma GCC optimize("-fgcse-lm")
%:pragma GCC optimize("-fipa-sra")
%:pragma GCC optimize("-ftree-pre")
%:pragma GCC optimize("-ftree-vrp")
%:pragma GCC optimize("-fpeephole2")
%:pragma GCC optimize("-ffast-math")
%:pragma GCC optimize("-fsched-spec")
%:pragma GCC optimize("unroll-loops")
%:pragma GCC optimize("-falign-jumps")
%:pragma GCC optimize("-falign-loops")
%:pragma GCC optimize("-falign-labels")
%:pragma GCC optimize("-fdevirtualize")
%:pragma GCC optimize("-fcaller-saves")
%:pragma GCC optimize("-fcrossjumping")
%:pragma GCC optimize("-fthread-jumps")
%:pragma GCC optimize("-funroll-loops")
%:pragma GCC optimize("-fwhole-program")
%:pragma GCC optimize("-freorder-blocks")
%:pragma GCC optimize("-fschedule-insns")
%:pragma GCC optimize("inline-functions")
%:pragma GCC optimize("-ftree-tail-merge")
%:pragma GCC optimize("-fschedule-insns2")
%:pragma GCC optimize("-fstrict-aliasing")
%:pragma GCC optimize("-fstrict-overflow")
%:pragma GCC optimize("-falign-functions")
%:pragma GCC optimize("-fcse-skip-blocks")
%:pragma GCC optimize("-fcse-follow-jumps")
%:pragma GCC optimize("-fsched-interblock")
%:pragma GCC optimize("-fpartial-inlining")
%:pragma GCC optimize("no-stack-protector")
%:pragma GCC optimize("-freorder-functions")
%:pragma GCC optimize("-findirect-inlining")
%:pragma GCC optimize("-fhoist-adjacent-loads")
%:pragma GCC optimize("-frerun-cse-after-loop")
%:pragma GCC optimize("inline-small-functions")
%:pragma GCC optimize("-finline-small-functions")
%:pragma GCC optimize("-ftree-switch-conversion")
%:pragma GCC optimize("-foptimize-sibling-calls")
%:pragma GCC optimize("-fexpensive-optimizations")
%:pragma GCC optimize("-funsafe-loop-optimizations")
%:pragma GCC optimize("inline-functions-called-once")
%:pragma GCC optimize("-fdelete-null-pointer-checks")
#include<bits/stdc++.h>
using namespace std;
struct Tree
{
int x;
}tree[35][800005];
int n,m,x,y,op,tmp[35],f[800005];
char c[500005];
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
inline void up_all(int k) {for(register int i=0;i<26;++i) tree[i][k].x=tree[i][k<<1].x+tree[i][k<<1|1].x;}
inline void Add(int k,int l,int r,int pl)
{
f[k]=pl;
for(int i=0;i<26;++i) tree[i][k].x=0;
tree[pl][k].x=r-l+1;
}
inline void down(int k,int l,int r)
{
if(f[k]==-1) return;
if(l!=r)
{
int mid=(l+r)>>1;
Add(k<<1,l,mid,f[k]);
Add(k<<1|1,mid+1,r,f[k]);
}
f[k]=-1;
}
inline void build(int k,int l,int r)
{
if(l==r)
{
tree[c[l]-'a'][k].x=1;
return;
}
int mid=(l+r)>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
up_all(k);
}
inline int get_sum(int k,int l,int r,int L,int R,int pl)
{
if(L<=l&&r<=R)
{
return tree[pl][k].x;
}
down(k,l,r);
int mid=(l+r)>>1;
int re=0;
if(L<=mid) re+=get_sum(k<<1,l,mid,L,R,pl);
if(mid+1<=R) re+=get_sum(k<<1|1,mid+1,r,L,R,pl);
return re;
}
inline void add_num(int k,int l,int r,int L,int R,int pl)
{
if(L>R) return;
if(L<=l&&r<=R)
{
Add(k,l,r,pl);
return;
}
down(k,l,r);
int mid=(l+r)>>1;
if(L<=mid) add_num(k<<1,l,mid,L,R,pl);
if(mid+1<=R) add_num(k<<1|1,mid+1,r,L,R,pl);
up_all(k);
}
inline void work_up(int x,int y)
{
for(register int i=0;i<26;++i) tmp[i]=get_sum(1,1,n,x,y,i);
int k=x;
for(register int i=0;i<26;++i)
{
add_num(1,1,n,k,k+tmp[i]-1,i);
k+=tmp[i];
if(k>y) return;
}
}
inline void work_down(int x,int y)
{
for(register int i=0;i<26;++i) tmp[i]=get_sum(1,1,n,x,y,i);
int k=x;
for(register int i=25;i>=0;--i)
{
add_num(1,1,n,k,k+tmp[i]-1,i);
k+=tmp[i];
if(k>y) return;
}
}
inline void write(int k,int l,int r)
{
if(l==r)
{
for(register int i=0;i<26;++i)
if (tree[i][k].x)
{
printf("%c",'a'+i);
return;
}
}
down(k,l,r);
int mid=(l+r)>>1;
write(k<<1,l,mid);
write(k<<1|1,mid+1,r);
}
signed main()
{
memset(f,-1,sizeof(f));
n=read(),m=read();
scanf("%s",c+1);
build(1,1,n);
while(m--)
{
x=read(),y=read(),op=read();
if(op==1)
{
work_up(x,y);
}
else
{
work_down(x,y);
}
}
write(1,1,n);
return 0;
}
至于为什么没用clock()强行卡时间……其实我不会
翻来覆去找到了某大佬要代码观摩,发现复杂度还是差的挺大。呜呜呜,我太菜了
AC代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,w,l,r,len[27];
char s[100010];
struct c
{
int l,r,x;
}tree[1000010];
void build(int k,int l,int r)
{
tree[k].l=l,tree[k].r=r;
if(l==r)
{
tree[k].x=s[l]-'a'+1;
return;
}
int mid=(l+r)/2;
build(k*2,l,mid);
build(k*2+1,mid+1,r);
if(tree[k*2].x==tree[k*2+1].x)
tree[k].x=tree[k*2].x;
}
void up(int k,int x,int y)
{
if(tree[k].l>=x&&tree[k].r<=y&&tree[k].x!=0)
{
len[tree[k].x]+=tree[k].r-tree[k].l+1;
return;
}
if(tree[k].x)
{
tree[k*2].x=tree[k].x;
tree[k*2+1].x=tree[k].x;
}
int mid=(tree[k].l+tree[k].r)/2;
if(x<=mid)up(k*2,x,y);
if(y>mid)up(k*2+1,x,y);
}
void change(int k,int x,int y,int v)
{
if(tree[k].l>=x&&tree[k].r<=y)
{
tree[k].x=v;
return;
}
if(tree[k].x)
{
tree[k*2].x=tree[k].x;
tree[k*2+1].x=tree[k].x;
tree[k].x=0;
}
int mid=(tree[k].l+tree[k].r)/2;
if(x<=mid)change(k*2,x,y,v);
if(y>mid)change(k*2+1,x,y,v);
if(tree[k*2].x==tree[k*2+1].x)
tree[k].x=tree[k*2].x;
}
void print(int x)
{
if(tree[x].x)
{
int l=tree[x].r-tree[x].l+1;
for(int i=1;i<=l;i++)
putchar(tree[x].x+'a'-1);
return;
}
print(x*2);print(x*2+1);
}
int main()
{
scanf("%d%d",&n,&m);
scanf("%s",s+1);
build(1,1,n);
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&l,&r,&w);
memset(len,0,sizeof(len));
up(1,l,r);
int ll=l;
if(w==1)
{
for(int i=1;i<=26;i++)
{
if(len[i])
{
change(1,ll,ll+len[i]-1,i);
ll+=len[i];
}
}
}
if(w==0)
{
for(int i=26;i>0;i--)
{
if(len[i])
{
change(1,ll,ll+len[i]-1,i);
ll+=len[i];
}
}
}
}
print(1);
return 0;
}
这道题的总体思路我就不啰嗦了,你搜到这篇博客说明你肯定有书,书上讲的应该比我好。我着重分析一下大佬的码到底优在哪里,以至于复杂度差的如此之多。
最明显的差别在于求每个字符的区间长度上,AC代码只用了一个函数就完成了,而50分代码则先后调用了三个函数。主要区别在于:50分代码是通过先移动大区间,找到小区间,在小区间中找到某一种字符的长度,然后累加,而AC代码则是通过枚举字符区间的左右端点判断该区间是否在目标区间内,然后直接加。 这样效率就会高很多,而且50分代码顺序倒序要分别求一下每个字符的区间长度,这样效率无疑减半。
通过这道题,我们可以知道线段树的题用的是线段树的思想,而不是死套模板,要灵活变通。