P5522 [yLOI2019] 棠梨煎雪
听着<<棠梨煎雪>>写棠梨煎雪,太美妙了!
题意:
对于一些给定的字符串,每个字符串只包含\(0,1\)以及?号。对于一个字符串\(s_1\),如果另一个不包含?号的串\(s_2\)对于每一个位置\(i\)满足:当\(s_{1,i}\)不为?,\(s_{2,i}=s_{1,i}\),反之任意即可;就说\(s_2\)匹配\(s_1\)。
题目要求我们在线求出对于一段区间\([l,r]\)之间所有字符串都匹配的字符串共有多少,同时支持将位置\(x\)的字符串修改的操作。
分析:
可以发现,\(n\)十分的微小,仅仅只有\(30\),所以我们可以发现一种显然的方法:暴力地开30棵线段树,然后把每个字符串每个字符的状态全部记录下来,简单易懂的操作。
不过同样显然地,时间复杂度\(O(q~log_mn)\),题面给出的\(q\)最大为\(10^6\),\(m\)最大为\(10^5\),这是我们就会发现它暴了。所以,我们需要寻求更高效地方法,比如把\(n\)消去就不失为一种不错的选择,接下来我们就探讨如何将\(n\)消去。
注意到,一个字符的状态数很少,只存在3种,所以我们可以往位运算上思考。单纯一个整数不够表示,我们就是用两个整数来表示一个字符串的状态。
对于一个字符串\(s_x\),我们设整数\(p_x\)的二进制形式表示一个字符串某位是否确定,即是否为'?,比方说一个串"1100?",的\(p\)就为\((11110) _2\); 又设\(q_x\)的二进制形式表示一个字符串某位具体是什么,其中'0'为0,'1'为1,'?'为0,比方还是"1100?",它的\(q\)就为\((11000)_2\)。这样就可以简单地把一个串的主要信息表示出来了。
然后就是问题的重点:如何合并两个字符串。现在假设有两个字符串\(s_1,s_2\),那我们相应的就有\(p_1,q_1,p_2,q_2\),意义同上。
接下来,我们可以设\(p=p1~xor~p2\),发现此时在\(p\)之中,如果对于某一位\(i\)有\(p_{1,i}=p_{2,i}\),那么\(p_i\)就为0,反之为1(异或的定义),所以\(p\)的意义即是合法情况下对于两个串\(s_1,s_2\)允许出现不同的位置,因为如果两个串的某个位置都确定,两个串的该位置必须相等,不然还可以不同。
再往后,我们仿照上面形式设\(q=q_1~xor~q_2\),然后\(q\)表示的就是两个串实际不同的的位置。比如对于串"1100?"与"10001"的\(p\)为\((00001)_2\),\(q\)就为\((01001)_2\)。这时我们根据定义就会发现,如果在\(q\)内为1的位置,在\(p\)内不为1,说明这两个串是冲突的,写成位运算判断就是\((p|q)!=p\),两串冲突。
万一两串经过上述判断不冲突,我们就可以使合并后的\(p_x=p_1|p_2,q_x=q_1|q_2\),毕竟根据定义,两串只要其中一个串的某位置确定了,合并后该位置必定确定,而该位置的确定值也是由相似的方式决定的。
上述说完,我们成功将\(O(n)\)的合并操作优化为了\(O(1)\),此时只用将其套入线段树内便可以以\(O(q~logm)\)的时间复杂度解决本题。
实现:
本题好像也没什么注意事项,思路对了还是很好搞得,那就直接放代码把:
#include<algorithm>
#include<stdio.h>
#include<queue>
#define maxn 100010
using namespace std;
int n,m,t;
char s[maxn][35];
struct tree {
int l,r;
int p,q,tag;
} T[maxn<<2];
int ls(int x) {return x<<1;}
int rs(int x) {return x<<1|1;}
void push_up(tree &x,tree l,tree r) {
if(l.tag || r.tag) {x.tag=1; return;}
int ps=l.p^r.p,qs=l.q^r.q;
x.tag=0;
if((ps|qs)!=ps) x.tag=1;
else x.p=l.p|r.p,x.q=l.q|r.q;
}
void out(int x) {
for(int i=0;i<n;i++) {
if(x&(1<<i)) printf("1");
else printf("0");
}
printf("\n");
}
void tree_pre(int x,int l,int r) {
T[x].l=l,T[x].r=r;
if(l==r) {
for(int i=0;i<n;i++) {
if(s[l][i]=='?') continue;
int add=(1<<i); T[x].p+=add;
if(s[l][i]=='1') T[x].q+=add;
}
return;
}
int mid=(l+r)>>1;
tree_pre(ls(x),l,mid);
tree_pre(rs(x),mid+1,r);
push_up(T[x],T[ls(x)],T[rs(x)]);
}
void modify(int x,int pos) {
if(T[x].l==T[x].r) {
T[x].p=0,T[x].q=0;
for(int i=0;i<n;i++) {
if(s[pos][i]=='?') continue;
int add=(1<<i); T[x].p+=add;
if(s[pos][i]=='1') T[x].q+=add;
}
T[x].tag=0;
return;
}
if(T[ls(x)].r>=pos) modify(ls(x),pos);
if(T[rs(x)].l<=pos) modify(rs(x),pos);
push_up(T[x],T[ls(x)],T[rs(x)]);
}
tree query(int x,int l,int r) {
if(T[x].l>=l && T[x].r<=r) return T[x];
tree ans;
if(T[ls(x)].r>=l && T[rs(x)].l<=r) {
push_up(ans,query(ls(x),l,r),query(rs(x),l,r));
return ans;
}
if(T[ls(x)].r>=l) return query(ls(x),l,r);
else return query(rs(x),l,r);
}
int main() {
// freopen("c.in","r",stdin);
// freopen("c.out","w",stdout);
scanf("%d%d%d",&n,&m,&t);
for(int i=1;i<=m;i++) {
scanf("%s",s[i]);
}
tree_pre(1,1,m);
int all=0;
for(int i=1;i<=t;i++) {
int opt; scanf("%d",&opt);
if(opt==0) {
int l,r; scanf("%d%d",&l,&r);
tree x=query(1,l,r);
if(!x.tag) {
int ans=n;
for(int j=0;j<n;j++) {
if(x.p&(1<<j)) ans--;
}
int sum=1;
for(int j=1;j<=ans;j++) {
sum*=2;
}
all^=sum;
// printf("%d %d\n",sum,all);
}
}
else {
int pos;
scanf("%d",&pos);
scanf("%s",s[pos]);
modify(1,pos);
}
}
printf("%d",all);
return 0;
}