题解:P14963 [LBA-OI R2 B] 何意味
my_dream666 · · 题解
前言
题目传送门。
感谢出题人的这道题,使我多积累一个 trick,并引发我的一系列思考。
不足或补充欢迎大家指出。
思路
注意到只要满足
所以对于一组询问 Yes。
由此我们得到
::::info[code]
#include<cstdio>
#include<vector>
using namespace std;
const int N=5e5+10;
int a[N],b[N];
vector <int> c,d;
void read()
{
return ;
}
template <typename T,typename ...Args>
void read(T &x,Args &...args)
{
x=0;
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
read(args...);
}
bool check()
{
for(int i=0;i<c.size();i++)
{
if(c[i]!=d[i]) return false;
}
return true;
}
int main()
{
int n,m,opt,l1,r1,l2,r2,x,y;
read(n,m);
for(int i=1;i<=n;i++) read(a[i]);
for(int i=1;i<=n;i++) read(b[i]);
for(int i=1;i<=m;i++)
{
read(opt);
if(opt==1)
{
read(l1,r1,l2,r2);
c.clear(); d.clear();
c.push_back(-1); d.push_back(-1);
for(int j=l1;j<=r1;j++)
{
if(a[j]==c.back()) c.pop_back();
else c.push_back(a[j]);
}
for(int j=l2;j<=r2;j++)
{
if(b[j]==d.back()) d.pop_back();
else d.push_back(b[j]);
}
if(c.size()==d.size())
{
if(check()) puts("Yes");
else puts("No");
}
else puts("No");
}
else if(opt==2)
{
read(x,y);
a[x]=y;
}
else
{
read(x,y);
b[x]=y;
}
}
return 0;
}
::::
我又对其进行了分块处理,虽然按理来说时间复杂度仍然是
::::info[分块 code]
#include<cstdio>
#include<vector>
using namespace std;
const int N=5e5+1000;
const int B=700;
int a[N],b[N],aa[B+20][B+20],bb[B+20][B+20],len[2][N];
vector <int> c,d;
void read()
{
return ;
}
template <typename T,typename ...Args>
void read(T &x,Args &...args)
{
x=0;
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
read(args...);
}
bool check()
{
for(int i=0;i<c.size();i++)
{
if(c[i]!=d[i]) return false;
}
return true;
}
void solve(int L,int R,vector <int> &e,int (*f)[B+20],int *g,int opt)
{
int l=(L+B-1)/B,r=(R+B-1)/B,ll,rr;
if(l==r)
{
for(int i=L;i<=R;i++)
{
if(g[i]==e.back()) e.pop_back();
else e.push_back(g[i]);
}
}
else
{
rr=l*B;
for(int i=L;i<=rr;i++)
{
if(g[i]==e.back()) e.pop_back();
else e.push_back(g[i]);
}
for(int i=l+1;i<r;i++)
{
ll=(i-1)*B+1; rr=i*B;
for(int j=1;j<=len[opt][i];j++)
{
if(f[i][j]==e.back()) e.pop_back();
else e.push_back(f[i][j]);
}
}
ll=(r-1)*B+1;
for(int i=ll;i<=R;i++)
{
if(g[i]==e.back()) e.pop_back();
else e.push_back(g[i]);
}
}
}
void update(int x,int y,int *f,int (*g)[B+20],int opt)
{
f[x]=y;
int cnt=(x+B-1)/B,l,r;
l=(cnt-1)*B+1; r=cnt*B;
len[opt][cnt]=0;
for(int i=l;i<=r;i++)
{
if(len[opt][cnt]&&f[i]==g[cnt][len[opt][cnt]]) len[opt][cnt]--;
else g[cnt][++len[opt][cnt]]=f[i];
}
}
int main()
{
int n,m,opt,l1,r1,l2,r2,x,y;
read(n,m);
for(int i=1;i<=n;i++) read(a[i]);
for(int i=1;i<=n;i++) read(b[i]);
int l,r,cnt;
cnt=(n/B)+1;
for(int i=1;i<=cnt;i++)
{
l=(i-1)*B+1; r=i*B;
for(int j=l;j<=r;j++)
{
if(len[0][i]&&a[j]==aa[i][len[0][i]]) len[0][i]--;
else aa[i][++len[0][i]]=a[j];
}
}
for(int i=1;i<=cnt;i++)
{
l=(i-1)*B+1; r=i*B;
for(int j=l;j<=r;j++)
{
if(len[1][i]&&b[j]==bb[i][len[1][i]]) len[1][i]--;
else bb[i][++len[1][i]]=b[j];
}
}
for(int i=1;i<=m;i++)
{
read(opt);
if(opt==1)
{
read(l1,r1,l2,r2);
c.clear(); d.clear();
c.push_back(-1); d.push_back(-1);
solve(l1,r1,c,aa,a,0);
solve(l2,r2,d,bb,b,1);
if(c.size()==d.size())
{
if(check()) puts("Yes");
else puts("No");
}
else puts("No");
}
else if(opt==2)
{
read(x,y);
update(x,y,a,aa,0);
}
else
{
read(x,y);
update(x,y,b,bb,1);
}
}
return 0;
}
::::
正解
发现我们的操作只是需要消去相邻相同的数,所以我们立刻想到了异或 hash,但是显然不仅仅如此。
例如
所以我们要有一种满足:
- 自身操作自身等于单位元
- 不满足交换律的操作。
对于不满足交换律,我们可以想到熟悉的矩阵。至于第一个条件,就是我们构造矩阵的方向。
所以定义一个
矩阵的单位元当然是:
所以有:
展开可得:
不妨令
对于
可得
然后考虑
由
所以我们用
::::info[补充]
对于上面的推导,是令
但是如果
又由
这样所有值都确定了,相当于构造了个寂寞。所以上面只写令其均不为
综上
这篇题解 的
这样我们就得出了最终做法,先用异或 hash,再用上面的方法对每个数构造矩阵,然后写个线段树维护区间矩阵乘积,单点修改,区间查询,最后每次查询看所得的矩阵是否相等就行了。
代码实现我用的是自然溢出,跑得挺快的,目前还在第一页。不放心的话可以加上取模。
::::info[code]
#include<cstdio>
#include<random>
#include<algorithm>
# define gc() (p1==p2&&(p2=(p1=buffer)+fread(buffer,1,1<<20,stdin),p1==p2)?EOF:*p1++)
using namespace std;
typedef unsigned int UI;
const int N=5e5+10;
const int MAXN=5e5;
char buffer[1<<20],*p1,*p2;
UI a[N],b[N],tmp[2][2],ans[2][2][2],X[N];
struct heyiwei
{
struct segnote
{
UI g[2][2];
void cheng(UI (*x)[2],UI (*y)[2]) //维护线段树节点的 push_up
{
g[0][0]=x[0][0]*y[0][0]+x[0][1]*y[1][0];
g[0][1]=x[0][0]*y[0][1]+x[0][1]*y[1][1];
g[1][0]=x[1][0]*y[0][0]+x[1][1]*y[1][0];
g[1][1]=x[1][0]*y[0][1]+x[1][1]*y[1][1];
}
void cal(UI (*x)[2]) //查询用的矩阵乘法
{
tmp[0][0]=x[0][0]; tmp[0][1]=x[0][1];
tmp[1][0]=x[1][0]; tmp[1][1]=x[1][1];
x[0][0]=g[0][0]*tmp[0][0]+g[0][1]*tmp[1][0];
x[0][1]=g[0][0]*tmp[0][1]+g[0][1]*tmp[1][1];
x[1][0]=g[1][0]*tmp[0][0]+g[1][1]*tmp[1][0];
x[1][1]=g[1][0]*tmp[0][1]+g[1][1]*tmp[1][1];
}
}segtree[N<<2];
void build_tree(int x,int left,int right,UI *c)
{
if(left==right)
{
segtree[x].g[0][0]=c[left]; segtree[x].g[0][1]=1-c[left];
segtree[x].g[1][0]=1+c[left]; segtree[x].g[1][1]=-c[left];
return ;
}
int mid=(left+right)>>1;
build_tree(x<<1,left,mid,c);
build_tree(x<<1|1,mid+1,right,c);
segtree[x].cheng(segtree[x<<1].g,segtree[x<<1|1].g);
}
void update(int x,int pos,int k,int l,int r)
{
if(l==r)
{
segtree[x].g[0][0]=k; segtree[x].g[0][1]=1-k;
segtree[x].g[1][0]=1+k; segtree[x].g[1][1]=-k;
return ;
}
int mid=(l+r)>>1;
if(pos<=mid) update(x<<1,pos,k,l,mid);
else update(x<<1|1,pos,k,mid+1,r);
segtree[x].cheng(segtree[x<<1].g,segtree[x<<1|1].g);
}
void query(int x,int left,int right,int opt,int l,int r)
{
if(left<=l&&r<=right)
{
segtree[x].cal(ans[opt]);
return ;
}
int mid=(l+r)>>1;
// 注意乘的顺序,我是每次右乘,故先算右边。具体因人而异
if(right>mid) query(x<<1|1,left,right,opt,mid+1,r);
if(left<=mid) query(x<<1,left,right,opt,l,mid);
}
}A,B;
void read()
{
return ;
}
template <typename T,typename ...Args>
void read(T &x,Args &...args)
{
x=0;
char ch=gc();
while(ch<'0'||ch>'9') ch=gc();
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=gc();
}
read(args...);
}
void init()// 随机数 异或 hash
{
random_device r;
mt19937 d(r());
uniform_int_distribution <int> rnd(1,2000000000);
for(int i=0;i<=MAXN;i++) X[i]=rnd(d);
}
bool check()
{
if(ans[0][0][0]==ans[1][0][0]&&ans[0][0][1]==ans[1][0][1]&&ans[0][1][0]==ans[1][1][0]&&ans[0][1][1]==ans[1][1][1]) return true;
return false;
}
int main()
{
init();
int n,m,l1,r1,l2,r2,x,y,opt;
read(n,m);
for(int i=1;i<=n;i++) read(a[i]),a[i]=X[a[i]];
for(int i=1;i<=n;i++) read(b[i]),b[i]=X[b[i]];
A.build_tree(1,1,n,a);
B.build_tree(1,1,n,b);
for(int i=1;i<=m;i++)
{
read(opt);
if(opt==1)
{
read(l1,r1,l2,r2);
ans[0][0][0]=ans[1][0][0]=ans[0][1][1]=ans[1][1][1]=1;
ans[0][0][1]=ans[1][0][1]=ans[0][1][0]=ans[1][1][0]=0;
A.query(1,l1,r1,0,1,n);
B.query(1,l2,r2,1,1,n);
if(check()) puts("Yes");
else puts("No");
}
else if(opt==2)
{
read(x,y);
y=X[y];
A.update(1,x,y,1,n);
}
else
{
read(x,y);
y=X[y];
B.update(1,x,y,1,n);
}
}
return 0;
}
::::
深入探究
矩阵运算
不感兴趣的可以跳过了。
还是用矩阵维护,但是我们可以用另外的方法,就是自定义矩阵运算法则。
可以看具体的应用 动态 DP。
我们在此题肯定不能用较高级的方法做,结果啥用没有,所以我们来研究位运算的法则。
我们把位运算都想一遍,能满足自定义矩阵乘法的就只用
还是简略说一下:因为只有这两个运算满足分配律。
然后此处有两种选择:
- 把乘法当作
\operatorname{or} ,加法当作\operatorname{and} 。 - 把乘法当作
\operatorname{and} ,加法当作\operatorname{or} 。
对于第
但是第
可以构造矩阵:
单位元是:
但是我写完发现跑的比普通的慢,为什么此处位运算比乘法和加法慢,我不理解了,其他地方几乎一样,希望这个方法仍有参考价值。
::::info[code]
#include<cstdio>
#include<random>
#include<algorithm>
# define e 4294967295u
# define gc() (p1==p2&&(p2=(p1=buffer)+fread(buffer,1,1<<20,stdin),p1==p2)?EOF:*p1++)
using namespace std;
typedef unsigned int UI;
const int N=5e5+10;
const int MAXN=5e5;
char buffer[1<<20],*p1,*p2;
UI a[N],b[N],tmp[2][2],ans[2][2][2],X[N];
struct heyiwei
{
struct segnote
{
UI g[2][2];
void cheng(UI (*x)[2],UI (*y)[2]) //此处不同
{
g[0][0]=(x[0][0]|y[0][0])&(x[0][1]|y[1][0]);
g[0][1]=(x[0][0]|y[0][1])&(x[0][1]|y[1][1]);
g[1][0]=(x[1][0]|y[0][0])&(x[1][1]|y[1][0]);
g[1][1]=(x[1][0]|y[0][1])&(x[1][1]|y[1][1]);
}
void cal(UI (*x)[2])
{
tmp[0][0]=x[0][0]; tmp[0][1]=x[0][1];
tmp[1][0]=x[1][0]; tmp[1][1]=x[1][1];
x[0][0]=(g[0][0]|tmp[0][0])&(g[0][1]|tmp[1][0]);
x[0][1]=(g[0][0]|tmp[0][1])&(g[0][1]|tmp[1][1]);
x[1][0]=(g[1][0]|tmp[0][0])&(g[1][1]|tmp[1][0]);
x[1][1]=(g[1][0]|tmp[0][1])&(g[1][1]|tmp[1][1]);
}
}segtree[N<<2];
void build_tree(int x,int left,int right,UI *c)
{
if(left==right)
{
segtree[x].g[0][0]=c[left]; segtree[x].g[0][1]=~c[left]; //此处不同
segtree[x].g[1][0]=~c[left]; segtree[x].g[1][1]=c[left];
return ;
}
int mid=(left+right)>>1;
build_tree(x<<1,left,mid,c);
build_tree(x<<1|1,mid+1,right,c);
segtree[x].cheng(segtree[x<<1].g,segtree[x<<1|1].g);
}
void update(int x,int pos,int k,int l,int r)
{
if(l==r)
{
segtree[x].g[0][0]=k; segtree[x].g[0][1]=~k;
segtree[x].g[1][0]=~k; segtree[x].g[1][1]=k; //此处不同
return ;
}
int mid=(l+r)>>1;
if(pos<=mid) update(x<<1,pos,k,l,mid);
else update(x<<1|1,pos,k,mid+1,r);
segtree[x].cheng(segtree[x<<1].g,segtree[x<<1|1].g);
}
void query(int x,int left,int right,int opt,int l,int r)
{
if(left<=l&&r<=right)
{
segtree[x].cal(ans[opt]);
return ;
}
int mid=(l+r)>>1;
if(right>mid) query(x<<1|1,left,right,opt,mid+1,r);
if(left<=mid) query(x<<1,left,right,opt,l,mid);
}
}A,B;
void read()
{
return ;
}
template <typename T,typename ...Args>
void read(T &x,Args &...args)
{
x=0;
char ch=gc();
while(ch<'0'||ch>'9') ch=gc();
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=gc();
}
read(args...);
}
void init()
{
random_device r;
mt19937 d(r());
uniform_int_distribution <int> rnd(1,2000000000);
for(int i=0;i<=MAXN;i++) X[i]=rnd(d);
}
bool check()
{
if(ans[0][0][0]==ans[1][0][0]&&ans[0][0][1]==ans[1][0][1]&&ans[0][1][0]==ans[1][1][0]&&ans[0][1][1]==ans[1][1][1]) return true;
return false;
}
int main()
{
init();
int n,m,l1,r1,l2,r2,x,y,opt;
read(n,m);
for(int i=1;i<=n;i++) read(a[i]),a[i]=X[a[i]];
for(int i=1;i<=n;i++) read(b[i]),b[i]=X[b[i]];
A.build_tree(1,1,n,a);
B.build_tree(1,1,n,b);
for(int i=1;i<=m;i++)
{
read(opt);
if(opt==1)
{
read(l1,r1,l2,r2);
ans[0][0][0]=ans[1][0][0]=ans[0][1][1]=ans[1][1][1]=0; //此处不同
ans[0][0][1]=ans[1][0][1]=ans[0][1][0]=ans[1][1][0]=e;
A.query(1,l1,r1,0,1,n);
B.query(1,l2,r2,1,1,n);
if(check()) puts("Yes");
else puts("No");
}
else if(opt==2)
{
read(x,y);
y=X[y];
A.update(1,x,y,1,n);
}
else
{
read(x,y);
y=X[y];
B.update(1,x,y,1,n);
}
}
return 0;
}
:::: ::::warning[警告] 但是我把自己 hack 了,我发现这样做会有一种奇妙的性质。
令
但是由于数据较水,代码还是过去了。
给出一组数据:
3 1
3 2 3
2 2 2
1 1 3 2 2
应该是 No,我这输出 Yes。
希望我上面写的对你还是有启发意义的。 ::::
非矩阵运算
至于非矩阵运算,我就简单提一句: