中文题意:
T TT 组数据
给你两个长度为 n nn 的01串 s , f , s,f,s,f,有 q qq 次询问。
每次询问有区间 [ l , r ] [\ l,r\ ][ l,r ] ,如果 [ l , r ] [\ l,r\ ][ l,r ] 同时包含0 00和1 11,则询问终止,否则你可以改变区间[ l , r ] [\ l,r\ ][ l,r ] 内严格小于 l e n l r len_{lr}lenlr 的数字。
问是否可以使得询问不终止,且经过 q qq 次询问后可以将s ss改为f ff。
前置知识:
线段树
没了
思路:
发现没法正序推过去(反正我不会),考虑根据询问逆推。
那么对于 f ff ,和 q 1 , q 2 q_{1},q_{2}q1,q2···q n q_{n}qn ,用 l i , r i l_{i},r_{i}li,ri 来表示 q i q_{i}qi , s i s_{i}si表示经过前 i ii 次询问后的字符串 s ss 。
对于第 n nn 次询问,当且仅当 s n − 1 s_{n-1}sn−1中的 [ l n , r n ] [l_{n},r_{n}][ln,rn] 全为 k kk ( k kk ∈ \in∈ ( 0 , 1 ) (0,1)(0,1) ) ,f ff 在 [ l n , r n ] [l_{n},r_{n}][ln,rn] 内(k ⊕ 1 k\oplus 1k⊕1)的数量n u m k ⊕ 1 num_{k\oplus 1}numk⊕1 < << l e n l r len_{lr}lenlr 时,
s n − 1 s_{n-1}sn−1 可转化 f ff 。
因此,我们可以对于 f ff 从 n nn 开始向前遍历询问。对于 [ l i , r i ] [l_{i},r_{i}][li,ri] , 将 [ l i , r i ] [l_{i},r_{i}][li,ri] 内数量较少的数字改为另一个数字。
显然,当 [ l i , r i ] [l_{i},r_{i}][li,ri] 内 n u m 1 = n u m 0 num_{1} = num_{0}num1=num0 时,询问会终止,因为改变量必须严格小于区间长度的一半。
遍历到最后判断 s ss 和经过转化的 f ff 是否相同就行了。
做法:
对于区间,查询和改变问题,我们可以用线段树在 l o g n log\ nlog n 的复杂度下解决。
首先对于 f ff 建立线段树,维护区间内 1 11 的数量。
对于区间修改,建立 l a z y lazylazy 标记,− 1 -1−1 表示不变,0 00 表示 l a z y lazylazy 下的区间全为0 00,1 11 表示 l a z y lazylazy 下的区间全为1 11。
p u s d o w n pusdownpusdown 操作:
inline void pushdown(int p,int l,int r)
{
if(laz[p]==-1)//未被标记跳过
return ;
int mid=(l+r)>>1;
if(laz[p])//标记为1
{
tr[p<<1]=(mid-l+1);
tr[p<<1|1]=(r-mid);
laz[p<<1]=laz[p<<1|1]=1;
laz[p]=-1;
return ;
}
tr[p<<1]=tr[p<<1|1]=0;//标记为0
laz[p<<1]=laz[p<<1|1]=0;
laz[p]=-1;
}
剩下的就是线段树的基本操作了。
Code:
#include<bits/stdc++.h>
#define N 240000
using namespace std;
int t,n,q;
char s[N],f[N];
int ql[N],qr[N],tr[N<<2],laz[N<<2];
inline int read()
{
char a=0;int w=1,x=0;
while(a<'0'||a>'9'){if(a=='-')w=-1;a=getchar();}
while(a<='9'&&a>='0'){x=(x<<3)+(x<<1)+(a^48);a=getchar();}
return x*w;
}
inline void pushdown(int p,int l,int r)
{
if(laz[p]==-1)//未被标记跳过
return ;
int mid=(l+r)>>1;
if(laz[p])//标记为1
{
tr[p<<1]=(mid-l+1);
tr[p<<1|1]=(r-mid);
laz[p<<1]=laz[p<<1|1]=1;
laz[p]=-1;
return ;
}
tr[p<<1]=tr[p<<1|1]=0;//标记为0
laz[p<<1]=laz[p<<1|1]=0;
laz[p]=-1;
}
void build(int p,int l,int r)//建树
{
laz[p]=-1;
if(l==r)
{
tr[p]=(f[l]^48);
return ;
}
int mid=(l+r)>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
tr[p]=tr[p<<1]+tr[p<<1|1];
}
int que(int p,int l,int r,int L,int R)//查询1的数量
{
if(L<=l&&r<=R)
return tr[p];
pushdown(p,l,r);
int mid=(l+r)>>1;
int ans=0;
if(mid>=L)
ans+=que(p<<1,l,mid,L,R);
if(mid<R)
ans+=que(p<<1|1,mid+1,r,L,R);
return ans;
}
void modify(int p,int l,int r,int L,int R,int opt)//区间修改
{
if(L<=l&&r<=R)
{
tr[p]=opt*(r-l+1);
laz[p]=opt;
return ;
}
pushdown(p,l,r);
int mid=(l+r)>>1;
if(mid>=L)
modify(p<<1,l,mid,L,R,opt);
if(mid<R)
modify(p<<1|1,mid+1,r,L,R,opt);
tr[p]=tr[p<<1]+tr[p<<1|1];
}
int main()
{
t=read();
while(t--)
{
n=read();
q=read();
int flag=1;
scanf("%s%s",(s+1),(f+1));
for(register int i=1;i<=q;i++)
{
ql[i]=read();
qr[i]=read();
}
build(1,1,n);
for(register int i=q;i>=1;i--)
{
int len=qr[i]-ql[i]+1;//区间长度
int num=que(1,1,n,ql[i],qr[i]);//查询区间内1的数量
if( num==len-num )//区间内0的数量为 len-num , 0和1数量相同时不可能成立
{
flag=0;
break;
}
modify(1,1,n,ql[i],qr[i],num>(len-num) );//区间修改
}
if(!flag)
{
printf("NO\n");
continue;
}
for(register int i=1;i<=n;i++)
{
int num=que(1,1,n,i,i);//取出经过q次询问后f的第i位
if(num!=(s[i]^48))//判断f和s是否相等,不相等退出
{
flag=0;
break;
}
}
if(!flag)
{
printf("NO\n");
continue;
}
printf("YES\n");
}
return 0;
}