Hetao P5493 [XRCOI Round 2] 好的跃迁 题解 [ 蓝 ] [ 线段树 ]
好的跃迁:蒟蒻的第二道公开赛题。
感觉这题挺显然的,一开始做法是线段树矩阵更是唐氏,交错和做法在最后验题的时候才发现。
#1∼3
暴力模拟即可。
#4∼6
每次修改之后暴力找循环节,然后每次 \(O(1)\) 查询。
#7∼9
观察到只有一个点,并且假设这个点为 \((a,b)\),那么点 \((x,y)\) 经过一次变换后会变为 \((2a-x,2b-y)\)。所以我们直接构造一个 \(3\times 3\) 的矩阵,进行矩阵快速幂即可。
初始矩阵如下:
递推矩阵如下:
#10∼13
只有一次查询,所以我们直接暴力修改,到修改时我们直接一次求解即可。
做法也是矩阵快速幂。因为矩阵乘法没有交换律,所以我们先要把所有点的矩阵全部乘起来,然后再用快速幂处理循环节,循环节之外剩下的一点直接暴力处理掉即可。
#14∼17
没有修改操作,但是起点不固定,所以我们可以采用破环为链的方式,并加上倍增数组来存矩阵,每次查询进行一次 \(O(\log n \times B^3)\) 的倍增跳,算出矩阵即可。
#18∼20
我们考虑把倍增数组换成线段树,在线段树上存矩阵,那么每次修改就是 \(O(B ^3 \log n)\) 的,查询也是 \(O(B ^3 \log n)\)。此处 \(B=3\)。其余细节和前文一致。
注意加上快读快写,以及取模的细节。
这个做法常数较大,无法通过所有测试点。
放一下这个做法的代码:
#include <bits/stdc++.h>
#define fi first
#define se second
#define lc (p<<1)
#define rc ((p<<1)|1)
using namespace std;
typedef long long ll;
typedef pair<int,int> pi;
inline ll llread(){
ll x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
inline int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
void write(int x){
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar(x%10+'0');
}
const int N=200005;
int q,n;
ll mod;
struct mat{
ll a[4][4];
mat(){memset(a,0,sizeof(a));}
mat operator*(const mat&t)const{
mat res;
for(int i=1;i<=3;i++)
{
for(int k=1;k<=3;k++)
{
ll r=a[i][k];
for(int j=1;j<=3;j++)
{
res.a[i][j]=(res.a[i][j]+r*t.a[k][j])%mod;
if(res.a[i][j]<0)res.a[i][j]+=mod;
}
}
}
return res;
}
}f[N];
mat qpow(mat a,ll b)
{
mat res;
for(int i=1;i<=3;i++)res.a[i][i]=1;
while(b)
{
if(b&1)res=res*a;
a=a*a;
b>>=1;
}
return res;
}
struct node{
int l,r;
mat v;
};
struct segtree{
node tr[4*N];
void build(int p,int ln,int rn)
{
tr[p]={ln,rn,f[ln]};
if(ln==rn)return;
int mid=(ln+rn)>>1;
build(lc,ln,mid);
build(rc,mid+1,rn);
tr[p].v=tr[lc].v*tr[rc].v;
}
void update(int p,int x,mat w)
{
if(tr[p].l==x&&tr[p].r==x)
{
tr[p].v=w;
return;
}
int mid=(tr[p].l+tr[p].r)>>1;
if(x<=mid)update(lc,x,w);
else update(rc,x,w);
tr[p].v=tr[lc].v*tr[rc].v;
}
mat query(int p,int ln,int rn)
{
if(ln<=tr[p].l&&tr[p].r<=rn)return tr[p].v;
int mid=(tr[p].l+tr[p].r)>>1;
if(rn<=mid)return query(lc,ln,rn);
if(ln>=mid+1)return query(rc,ln,rn);
mat tmp;
tmp=query(lc,ln,rn)*query(rc,ln,rn);
return tmp;
}
}seg1;
void construct_dp(mat &m,ll a,ll b)
{
m.a[1][1]=1;
m.a[1][2]=2*a%mod;
m.a[1][3]=2*b%mod;
m.a[2][2]=-1;
m.a[3][3]=-1;
}
void construct_s(mat &m,ll a,ll b)
{
m.a[1][1]=1;
m.a[1][2]=a;
m.a[1][3]=b;
}
int main()
{
q=read(),n=read(),mod=read();
for(int i=1;i<=n;i++)
{
ll a,b;
a=read();
b=read();
construct_dp(f[i],a,b);
construct_dp(f[i+n],a,b);
}
seg1.build(1,1,2*n);
while(q--)
{
int op;
op=read();
if(op==1)
{
ll i,a,b;
i=read(),a=read(),b=read();
mat tmp;
construct_dp(tmp,a,b);
seg1.update(1,i,tmp);
seg1.update(1,i+n,tmp);
}
else
{
ll a,b,s,k;
a=read(),b=read(),s=read(),k=llread();
mat st;
construct_s(st,a,b);
mat tmp1=seg1.query(1,s,s+n-1);
st=st*qpow(tmp1,k/n);
if(k%n>0)
{
mat tmp2=seg1.query(1,s,s+k%n-1);
st=st*tmp2;
}
write(st.a[1][2]);
putchar(' ');
write(st.a[1][3]);
putchar('\n');
}
}
return 0;
}
#21~25
Sol.1
观察到横纵坐标相互独立,所以我们可以开两颗线段树,对横纵坐标分别进行计算。其余部分和之前常数较大的版本一样。此时矩阵被缩小至 \(2 \times 2\),线段树查询和修改复杂度均为 \(O(B^3 \log n)\)。其中 \(B=2\)。
对破环成链的部分可以不开两倍空间维护,直接拼接即可做到更小的常数。
#include <bits/stdc++.h>
#define fi first
#define se second
#define lc (p<<1)
#define rc ((p<<1)|1)
using namespace std;
typedef long long ll;
typedef pair<int,int> pi;
inline ll llread(){
ll x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
inline int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
void write(int x){
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar(x%10+'0');
}
const int N=200005;
int q,n,lx;
ll mod;
struct mat{
ll a[2][2];
mat(){memset(a,0,sizeof(a));}
mat operator*(const mat&t)const{
mat res;
for(int i=0;i<=1;i++)
{
for(int k=0;k<=1;k++)
{
ll r=a[i][k];
for(int j=0;j<=1;j++)
{
res.a[i][j]=(res.a[i][j]+r*t.a[k][j])%mod;
if(res.a[i][j]<0)res.a[i][j]+=mod;
}
}
}
return res;
}
}mt1[N],mt2[N];
inline mat qpow(mat a,ll b)
{
mat res;
res.a[0][0]=1;
res.a[1][1]=1;
while(b)
{
if(b&1)res=res*a;
a=a*a;
b>>=1;
}
return res;
}
struct node{
int l,r;
mat v;
};
struct segtree{
node tr[4*N];
void build(int p,int ln,int rn)
{
if(lx==1)tr[p]={ln,rn,mt1[ln]};
else tr[p]={ln,rn,mt2[ln]};
if(ln==rn)return;
int mid=(ln+rn)>>1;
build(lc,ln,mid);
build(rc,mid+1,rn);
tr[p].v=tr[lc].v*tr[rc].v;
}
void update(int p,int x,mat w)
{
if(tr[p].l==x&&tr[p].r==x)
{
tr[p].v=w;
return;
}
int mid=(tr[p].l+tr[p].r)>>1;
if(x<=mid)update(lc,x,w);
else update(rc,x,w);
tr[p].v=tr[lc].v*tr[rc].v;
}
mat query(int p,int ln,int rn)
{
if(ln<=tr[p].l&&tr[p].r<=rn)return tr[p].v;
int mid=(tr[p].l+tr[p].r)>>1;
if(rn<=mid)return query(lc,ln,rn);
if(ln>=mid+1)return query(rc,ln,rn);
mat tmp;
tmp=query(lc,ln,rn)*query(rc,ln,rn);
return tmp;
}
mat range_query(int x,int y)
{
if(x>n)x-=n,y-=n;
if(y<=n)return query(1,x,y);
return query(1,x,n)*query(1,1,y-n);
}
}seg1,seg2;
inline void construct_dp(mat &m,ll a)
{
m.a[0][0]=1;
m.a[0][1]=2*a%mod;
m.a[1][1]=-1;
}
inline void construct_s(mat &m,ll a)
{
m.a[0][0]=1;
m.a[0][1]=a;
}
int main()
{
q=read(),n=read(),mod=read();
for(int i=1;i<=n;i++)
{
ll a,b;
a=read();
b=read();
construct_dp(mt1[i],a);
construct_dp(mt2[i],b);
}
lx=1;
seg1.build(1,1,n);
lx=2;
seg2.build(1,1,n);
while(q--)
{
int op;
op=read();
if(op==1)
{
ll i,a,b;
i=read(),a=read(),b=read();
mat tmp;
construct_dp(tmp,a);
seg1.update(1,i,tmp);
construct_dp(tmp,b);
seg2.update(1,i,tmp);
}
else
{
ll a,b,s,k;
a=read(),b=read(),s=read(),k=llread();
mat st;
construct_s(st,a);
mat tmp1=seg1.range_query(s,s+n-1);
st=st*qpow(tmp1,k/n);
if(k%n>0)
{
mat tmp2=seg1.range_query(s,s+k%n-1);
st=st*tmp2;
}
write(st.a[0][1]);
putchar(' ');
construct_s(st,b);
tmp1=seg2.range_query(s,s+n-1);
st=st*qpow(tmp1,k/n);
if(k%n>0)
{
mat tmp2=seg2.range_query(s,s+k%n-1);
st=st*tmp2;
}
write(st.a[0][1]);
putchar('\n');
}
}
return 0;
}
Sol.2
观察到式子是 \(2a-x\) 的形式,嵌套起来就是 \(2c-(2b-(2a-x))=2c-2b+2a-x\) 的交替和形式。于是我们开两颗线段树维护交替和,分几种情况考虑查询:
- 最后的 \(i\) 未跨过 \(n\)。
- 直接算交错和即可。
- 最后的 \(i\) 跨过了一次 \(n\)。
- 跨过 \(n\) 之前交错和和跨过 \(n\) 之后交错和分开算,最后加在一起即可。
- 最后的 \(i\) 至少跨过两次 \(n\)。
- 当 \(n\) 为奇数时。
- 因为奇偶性不同,所以大部分全部交错和会被抵消,剩下的一次交错和直接查询即可。再用第二类的方式处理散段即可。
- 当 \(n\) 为偶数时。
- 大体和前面一样,在第二类情况上查询全部交错和,乘上系数即可。
- 当 \(n\) 为奇数时。
时间复杂度 \(O(n\log n)\),比 Sol.1 的做法小几个常数。
代码是容易的,根据上面的模拟就好了,这里就不放了。

浙公网安备 33010602011771号