感谢卡老师上课分享的题单
CF558E
题意
Luogu
给定一个长度不超过10^5的字符串(小写英文字母),和不超过50000个操作。
每个操作 L R K 表示给区间[L,R]的字符串排序,K=1为升序,K=0为降序。
输出最终的字符串。
题解
因为字符集只有26,所以区间内重复元素个数很多,排序后相同元素会聚拢,即区间覆盖
考虑用线段树记录区间内每个字母出现的次数,然后按序区间覆盖即可
代码
#include<bits/stdc++.h>
using namespace std;
#define re register
#define ll long long
#define get getchar()
#define in inline
in int read()
{
int t=0, x=1; char ch=get;
while((ch<'0' || ch>'9') && ch!='-')ch=get;
if(ch=='-') ch=get, x=-1;
while(ch<='9' && ch>='0') t=t*10+ch-'0', ch=get;
return x*t;
}
const int _=1e5+5;
int n,m;
char s[_];
int t[_<<3][27],la[_<<3],ans[27];
in void add(int k,int l,int r,int x,int y)
{
if(l==r) {
t[k][y]++;
return;
}
int mid=l+r>>1;
if(x<=mid) add(k<<1, l, mid, x, y);
else add(k<<1|1, mid+1, r, x, y);
t[k][y]=t[k<<1][y]+t[k<<1|1][y];
}
in void pushdown(int k,int l,int r)
{
if(la[k]==0) return ;
la[k<<1]=la[k<<1|1]=la[k];
for(re int i=1;i<=26;i++) t[k<<1][i]=t[k<<1|1][i]=0;
int mid=l+r>>1;
t[k<<1][la[k]]=mid-l+1;
t[k<<1|1][la[k]]=r-mid;
la[k]=0;
}
in void query(int k,int l,int r,int x,int y)
{
if(x<=l && r<=y)
{
for(re int i=1;i<=26;i++) ans[i]+=t[k][i];
return;
}
int mid=l+r>>1;
pushdown(k,l,r);
if(x<=mid) query(k<<1, l, mid, x, y);
if(y>mid) query(k<<1|1, mid+1, r, x, y);
}
in void update(int k,int l,int r,int x,int y,int z)
{
if(x<=l && r<=y) {
for(re int i=1;i<=26;i++) t[k][i]=0;
t[k][z]=r-l+1;
la[k]=z;
return;
}
pushdown(k,l,r);
int mid=l+r>>1;
if(x<=mid) update(k<<1, l, mid, x, y, z);
if(y>mid) update(k<<1|1, mid+1, r, x, y, z);
for(re int i=1;i<=26;i++) t[k][i]=t[k<<1][i]+t[k<<1|1][i];
}
int main()
{
n=read(), m=read();
scanf("%s",s+1);
for(re int i=1;i<=n;i++) add(1,1,n,i,s[i]-'a'+1);
for(re int i=1;i<=m;i++)
{
int l=read(), r=read(), z=read();
query(1,1,n,l,r);
int p=l;
if(z==1)
for(re int j=1;j<=26;j++)
{
if(ans[j]==0) continue;
update(1,1,n,p,p+ans[j]-1,j);
p+=ans[j];
}
else
for(re int j=26;j>=1;j--)
{
if(ans[j]==0) continue;
update(1,1,n,p,p+ans[j]-1,j);
p+=ans[j];
}
memset(ans,0,sizeof(ans));
}
for(re int i=1;i<=n;i++)
{
query(1,1,n,i,i);
int k=0;
for(re int j=1;j<=26;j++) if(ans[j]) {k=j;break;}
//极其愚蠢的输出方式:查询每个点的字符集,找那个字符有值/kk
ans[k]=0;
printf("%c",char(k-1+'a'));
}
return 0;
}
CF438D
题意
Luogu
给定数列,区间查询和,区间取模,单点修改。
题解
老套路了
因为取模次数有限,最多log次
考虑暴力修改,并记录区间最大值,当最大值小于模数时直接跳过
#include<bits/stdc++.h>
using namespace std;
#define re register
#define ll long long
#define get getchar()
#define in inline
in int read()
{
int t=0, x=1; char ch=get;
while((ch<'0' || ch>'9') && ch!='-') ch=get;
if(ch=='-') ch=get, x=-1;
while(ch<='9' && ch>='0') t=t*10+ch-'0', ch=get;
return t*x;
}
const int _=2e5+1;
int n,m,mx[_<<4];
ll sum[_<<4];
in void pushup(int k)
{
sum[k]=sum[k<<1]+sum[k<<1|1];
mx[k]=max(mx[k<<1],mx[k<<1|1]);
}
in void add(int k,int l,int r,int x,int y)
{
if(l==r)
{
mx[k]=sum[k]=y;
return ;
}
int mid=l+r>>1;
if(x<=mid) add(k<<1, l, mid, x, y);
else add(k<<1|1, mid+1, r, x, y);
pushup(k);
}
in void mod(int k,int l,int r,int x,int y,int p)
{
if(mx[k]<p) return; //区间最大值小于模数
if(l==r)
{
mx[k]=sum[k]%=p;
return;
}
int mid= l+r>>1;
if(x<=mid) mod(k<<1, l, mid, x, y, p);
if(y>mid) mod(k<<1|1, mid+1, r, x, y, p);
pushup(k);
}
in ll query(int k,int l,int r,int x,int y)
{
if(x<=l && r<=y) return sum[k];
int mid=l+r>>1;
ll s=0;
if(x<=mid) s+=query(k<<1, l, mid, x, y);
if(y>mid) s+=query(k<<1|1, mid+1, r, x, y);
return s;
}
int main()
{
n=read(), m=read();
for(re int i=1;i<=n;i++) add(1,1,n,i,read());
for(re int i=1;i<=m;i++)
{
int opt=read();
if(opt==1) {
int l=read(), r=read();
printf("%lld
",query(1,1,n,l,r));
}
if(opt==2) {
int l=read(), r=read(), p=read();
mod(1,1,n,l,r,p);
}
if(opt==3) {
int x=read(), y=read();
add(1,1,n,x,y);
}
}
return 0;
}
CF914D
题意
luogu
给定一个长为n的序列,m次操作
操作1: 询问一个区间可否在最多修改一个数后区间gcd=x
操作2: 单点修改
题解
区间gcd=x,不难发现若是区间gcd%x=0则可通过修改一个数符合题意,
所以维护区间gcd,每次查询区间中mod x不为0的个数,若大于1,则非法
代码
#include<bits/stdc++.h>
using namespace std;
#define re register
#define ll long long
#define get getchar()
#define in inline
in int read()
{
int t=0, x=1; char ch=get;
while((ch<'0' || ch>'9') && ch!='-')ch=get;
if(ch=='-' ) ch=get, x=-1;
while(ch<='9' && ch>='0') t=t*10+ch-'0', ch=get;
return t*x;
}
const int _=5e5+1;
int n,m,g[_<<4];
in int gcd(int x,int y)
{
if(x==-1 || y==-1) return max(x,y);
re int t;
while(y!=0)
t=x, x=y,y=t%y;
return x;
}
in void add(int k,int l,int r,int x,int y)
{
if(l==r) {g[k]=y; return;}
int mid=l+r>>1;
if(x<=mid) add(k<<1, l, mid, x, y);
else add(k<<1|1, mid+1, r, x, y);
g[k]=gcd(g[k<<1|1],g[k<<1]);
}
int cnt; //记录%p不为0的个数
in void query(int k,int l,int r,int x,int y,int p)
{
if(g[k]%p==0) return;
if(cnt>1) return; //大于1则无解,直接退出
if(l==r)
{
if(g[k]%p>0) cnt++;
return;
}
int mid=l+r>>1;
if(x<=mid) query(k<<1, l, mid, x, y, p);
if(y>mid) query(k<<1|1, mid+1, r, x, y, p);
}
int main()
{
memset(g,-1,sizeof(g));
n=read();
for(re int i=1; i<=n; i++) add(1,1,n,i,read());
m=read();
for(re int i=1;i<=m;i++)
{
int type=read(),l=read(),r=read();
if(type==1)
{
int x=read();
cnt=0;
query(1,1,n,l,r,x);
if(cnt<=1) puts("YES");
else puts("NO");
}
else add(1,1,n,l,r);
}
return 0;
}
CF920F
题意
洛谷
给定 n 个数的数组 a,m 次操作。操作有两种:
操作1: 将给定区间中的数都变为他们的约数个数
操作2: 查询区间和
题解
和之前CF438D的思路很像,也都是老套路了.
不难发现大部分数经过几次操作1后都会变成1或者2
然后就是老套路了:
维护区间和,并记录标记当前区间是否都为1,2
[TJOI2016]排序
题意
Luogu
给定一个1~n的排列,m次操作,每次操作将某一区间升序或降序排序,求最后位置p上的数
题解
此题思路很妙,值得借鉴
考虑我们之前 CF558E 这题,为啥能直接做,而这里不行呢?
有没有办法把那题的做法搬运到这里来呢?
自然是有的
考虑这题只要求某个位置的值,所以我们实际上是可以"不求甚解"的,
不需要关心其他位置的值,所以相对于该点而言,其他位置上的值与它的关系只有大于或是小于
考虑二分答案
直接二分p位置最后的值mid,将大于等于它的数全当成1,小于的数全当成0,然后按CF558E的做法跑
若最后该点值不为1,则说明大于等于mid的值是不可能作为答案的,否则反之
代码
#include<bits/stdc++.h>
using namespace std;
#define re register
#define ll long long
#define get getchar()
#define in inline
in int read()
{
int t=0; char ch=get;
while(ch<'0' || ch>'9') ch=get;
while(ch<='9' && ch>='0') t=t*10+ch-'0', ch=get;
return t;
}
const int _=2e5+4;
struct question{
int l,r,opt;
}q[_];
int n,m,sum[_<<3][2],Q,a[_],lazy[_<<3],b[_];
in void pushup(int k) { sum[k][0]=sum[k<<1][0]+sum[k<<1|1][0], sum[k][1]=sum[k<<1][1]+sum[k<<1|1][1]; }
in void build(int k,int l,int r,int lim)
{
lazy[k]=-1;
if(l==r) {
if(a[l]>=lim) sum[k][1]=1,sum[k][0]=0;
else sum[k][0]=1,sum[k][1]=0;
return;
}
int mid=l+r>>1;
build(k<<1,l,mid,lim),build(k<<1|1,mid+1,r,lim);
pushup(k);
}
in void pushdown(int k,int l,int r) {
if(lazy[k]==-1) return;
int qwe=lazy[k],mid=l+r>>1;
lazy[k]=-1;
lazy[k<<1]=lazy[k<<1|1]=qwe;
sum[k<<1][qwe]=mid-l+1;
sum[k<<1|1][qwe]=r-mid;
sum[k<<1][qwe^1]=sum[k<<1|1][qwe^1]=0;
}
in void update(int k,int l,int r,int x,int y,int z)
{
if(x>y) return;
if(x<=l && r<=y) {sum[k][z]=r-l+1,sum[k][z^1]=0,lazy[k]=z; return ;}
pushdown(k,l,r);
int mid=l+r>>1;
if(x<=mid) update(k<<1,l,mid,x,y,z);
if(y>mid) update(k<<1|1,mid+1,r,x,y,z);
pushup(k);
}
in int query(int k,int l,int r,int x,int y,int z)
{
if(x<=l && r<=y) return sum[k][z];
pushdown(k,l,r);
int mid=l+r>>1,s=0;
if(x<=mid) s+=query(k<<1,l,mid,x,y,z);
if(y>mid) s+=query(k<<1|1,mid+1,r,x,y,z);
return s;
}
in bool check(int k)
{
build(1,1,n,k);
for(re int i=1;i<=m;i++)
{
if(q[i].opt==1) { //按先1,再0进行区间覆盖,即降序排序
int cnt=query(1,1,n,q[i].l,q[i].r,1);
update(1,1,n,q[i].l,q[i].l+cnt-1,1);
update(1,1,n,q[i].l+cnt,q[i].r,0);
}
else { //升序
int cnt=query(1,1,n,q[i].l,q[i].r,0);
update(1,1,n,q[i].l,q[i].l+cnt-1,0);
update(1,1,n,q[i].l+cnt,q[i].r,1);
}
}
if(query(1,1,n,Q,Q,1)) return 1;
return 0;
}
int main()
{
n=read(),m=read();
for(re int i=1;i<=n;i++) a[i]=read();
for(re int i=1;i<=m;i++) q[i].opt=read(),q[i].l=read(),q[i].r=read();
Q=read();
int l=1,r=n,ans=0x3f3f3f3f;
while(l<=r) {
int mid=l+r>>1;
if(check(mid)) ans=mid, l=mid+1;
else r=mid-1;
}
cout<<ans<<endl;
}