题解:P15495 [ICPC 2025 APC] Tower of Hanoi
lailai0916 · · 题解
题意简述
每个圆盘初始位于三根柱子之一。支持单点修改圆盘所在柱,并询问一个连续大小区间内的圆盘全部移到
解题思路
先从小到大加入圆盘。设已经处理了
现在加入一个位于柱
若
这一步需要
递推中除了三个
加入任意一个圆盘,都是对
设较小圆盘区间的变换矩阵为
矩阵乘法满足结合律,所以可以直接用线段树维护。代码中的 merge(x,y) 表示把区间
一个询问区间之前没有已处理圆盘,初始状态为:
设询问得到的总矩阵为
线段树采用迭代写法。区间查询时,左侧累积器按从左到右追加节点,右侧累积器则把新节点放在已有结果之前,最后再按顺序合并两侧。补齐到线段树底层的空位置使用单位矩阵。
矩阵大小固定为
正确性证明
对已经处理的圆盘数
加入位于
区间矩阵按圆盘从小到大的顺序复合。merge(A,B) 返回先执行
询问以空圆盘集合的状态
参考代码
#include <bits/stdc++.h>
using namespace std;
const int N=1<<18;
const int mod=998244353;
struct node
{
int a[4][4];
}tr[N<<1];
node one()
{
node res={};
for(int i=0;i<4;i++)res.a[i][i]=1;
return res;
}
node gen(int x)
{
node res={};
for(int i=0;i<3;i++)
{
int p=x==i?i:3^x^i;
res.a[i][p]=1;
res.a[i][3]=x!=i;
}
res.a[3][3]=2;
return res;
}
node merge(const node &x,const node &y)
{
node res={};
for(int i=0;i<4;i++)
{
for(int j=0;j<4;j++)
{
for(int k=0;k<4;k++)res.a[i][j]=(res.a[i][j]+(long long)y.a[i][k]*x.a[k][j])%mod;
}
}
return res;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,q;
cin>>n>>q;
for(int i=0;i<N;i++)tr[N+i]=one();
for(int i=0;i<n;i++)
{
int x;
cin>>x;
tr[N+i]=gen(x-1);
}
for(int i=N-1;i;i--)tr[i]=merge(tr[i<<1],tr[i<<1|1]);
while(q--)
{
char op;
int x,y;
cin>>op>>x>>y;
if(op=='c')
{
int p=N+x-1;
tr[p]=gen(y-1);
while(p>>=1)tr[p]=merge(tr[p<<1],tr[p<<1|1]);
}
else
{
node l=one(),r=one();
x+=N-1;
y+=N;
while(x<y)
{
if(x&1)l=merge(l,tr[x++]);
if(y&1)r=merge(tr[--y],r);
x>>=1;
y>>=1;
}
cout<<merge(l,r).a[0][3]<<'\n';
}
}
return 0;
}