NOI2021 部分题目题解
D1T1 轻重边
Description
一个 \(n\) 个节点的树,一开始每条边都是轻边,支持两个操作:
- 给定 \(u,v\),对于 \(u\) 到 \(v\) 的路径上所有的点 \(x\),将与 \(x\) 相连的所有边变成轻边,再将 \(u,v\) 路径上的边变成重边。
- 询问 \(u,v\) 路径上有多少条重边。
\(n,m\le 10^5\)。
Solution
注意到整个过程中点 \(u\) 至多有两个重儿子(由重边相连的儿子),因此可以用树剖+两颗线段树暴力维护 \(u\) 的所有重儿子,这是我考场上的写法,常数较大且没有将代码带走。
事实上可以将操作 \(1\) 理解为对 \(u-v\) 路径上的所有点染一个新的颜色,那么一条边是重边当且仅当两端点颜色相同,于是可以用树链剖分后用线段树维护区间内用多少条重边,合并时只需要判断左区间的右端点与右区间的左端点颜色是否相同,这可以通过在线段树上维护一个 \(Lcol\) 和 \(Rcol\) 完成。最终复杂度 \(\mathcal O(n\log^2 n)\),当然也可以 \(LCT\) 做到 \(\mathcal O(n\log n)\)。
在近几年 \(NOI\) 中,这道题算是较难的一个 \(D1T1\),但想清楚后理应在 \(1h\) 内写完调完,我的考场做法较为复杂,用了 \(2h\) 才写完+卡常。
Code
#include<bits/stdc++.h>
using namespace std;
namespace iobuff{
const int LEN=10000000;
char in[LEN+5],out[LEN+5];
char *pin=in,*pout=out,*ed=in,*eout=out+LEN;
inline char gc(void){
return pin==ed&&(ed=(pin=in)+fread(in,1,LEN,stdin),ed==in)?EOF:*pin++;
}
inline void pc(char c){
pout==eout&&(fwrite(out,1,LEN,stdout),pout=out);
(*pout++)=c;
}
inline void flush(){fwrite(out,1,pout-out,stdout),pout=out;}
template<typename T> inline void read(T &x){
static int f;
static char c;
c=gc(),f=1,x=0;
while(c<'0'||c>'9') f=(c=='-'?-1:1),c=gc();
while(c>='0'&&c<='9') x=10*x+c-'0',c=gc();
x*=f;
}
template<typename T> inline void putint(T x,char div='\n'){
static char s[15];
static int top;
top=0;
while(x) s[top++]=x%10,x/=10;
!top?pc('0'),0:0;
while(top--) pc(s[top]+'0');
pc(div);
}
}
using namespace iobuff;
const int N=1e5+10;
struct node{
int v,nxt;
}e[N<<1];
int T,n,m,tot,first[N],cnt,fa[N],siz[N],dep[N],son[N],top[N],pos[N],num[N];
inline void add(int u,int v){e[++cnt].v=v;e[cnt].nxt=first[u];first[u]=cnt;}
void dfs(int u,int f){
fa[u]=f;
dep[u]=dep[f]+1;siz[u]=1;son[u]=0;
for(int i=first[u];i;i=e[i].nxt){
int v=e[i].v;
if(v^f){
dfs(v,u);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]]) son[u]=v;
}
}
}
void hld(int u,int tp){
top[u]=tp;pos[u]=++tot;num[tot]=u;
if(son[u]) hld(son[u],tp);
for(int i=first[u];i;i=e[i].nxt)
if(e[i].v!=fa[u]&&e[i].v!=son[u]) hld(e[i].v,e[i].v);
}
namespace SGT{
#define lc (p<<1)
#define rc (p<<1|1)
#define mid ((l+r)>>1)
const int M=N<<2;
int L[M],R[M],sum[M],tag[M];
inline void cover(int p,int l,int r,int c){
tag[p]=L[p]=R[p]=c;sum[p]=r-l;
}
inline void pushup(int p){
sum[p]=sum[lc]+sum[rc]+(R[lc]==L[rc]);
R[p]=R[rc];L[p]=L[lc];
}
inline void pushdown(int p,int l,int r){
if(!tag[p]) return ;
if(lc) cover(lc,l,mid,tag[p]);
if(rc) cover(rc,mid+1,r,tag[p]);
tag[p]=0;
}
void init(int p=1,int l=1,int r=n){
sum[p]=tag[p]=0;
if(l==r){L[p]=R[p]=l;return ;}
init(lc,l,mid);init(rc,mid+1,r);
pushup(p);
}
void modify(int p,int ql,int qr,int c,int l=1,int r=n){
if(ql<=l&&r<=qr){
cover(p,l,r,c);
return ;
}
pushdown(p,l,r);
if(ql<=mid) modify(lc,ql,qr,c,l,mid);
if(qr>mid) modify(rc,ql,qr,c,mid+1,r);
pushup(p);
}
int query(int p,int ql,int qr,int l=1,int r=n){
if(ql<=l&&r<=qr) return sum[p];
if(tag[p]) return min(qr,r)-max(ql,l);
pushdown(p,l,r);
int ans=0;
if(ql<=mid) ans+=query(lc,ql,qr,l,mid);
if(qr>mid) ans+=query(rc,ql,qr,mid+1,r);
if(ql<=mid&&qr>mid) ans+=(R[lc]==L[rc]);
return ans;
}
int getcol(int p,int x,int l=1,int r=n){
if(l==r) return R[p];
pushdown(p,l,r);
return x<=mid?getcol(lc,x,l,mid):getcol(rc,x,mid+1,r);
}
}
int col;
inline void modify(int u,int v){
++col;
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
SGT::modify(1,pos[top[u]],pos[u],col);
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
SGT::modify(1,pos[u],pos[v],col);
}
inline int query(int u,int v){
int ans=0;
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
if(top[u]!=u) ans+=SGT::query(1,pos[top[u]],pos[u]);
int x=SGT::getcol(1,pos[top[u]]),y=SGT::getcol(1,pos[fa[top[u]]]);
if(x==y&&x!=0) ans++;
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
if(u!=v) ans+=SGT::query(1,pos[u],pos[v]);
return ans;
}
int main(){
read(T);
while(T--){
read(n);read(m);
cnt=0;memset(first+1,0,sizeof(int)*(n));
for(int i=1,u,v;i<n;++i){
read(u);read(v);
add(u,v);add(v,u);
}
tot=0;col=n;
dfs(1,0);hld(1,1);
SGT::init();
while(m--){
int op,a,b;
read(op);read(a);read(b);
if(op==1) modify(a,b);
else putint(query(a,b),'\n');
}
}flush();
return 0;
}
D1T2 路径交点
Description
一个 \(k\) 层的有向图,第 \(i\) 层有 \(n_i\) 个顶点,保证 \(n_1=n_k\),且 \(n_1\le n_i\le 2n_1(2\le i\le k-1)\),有若干条从第 \(i\) 层点连向第 \(i+1\) 层点的边。
现在你需要在图中选择 \(n_1\) 条路径使得图中每个点至多在一条路径中出现一次。若两条路径 \(p,q\) 在第 \(i\) 层到第 \(i+1\) 层的部分 \(p_i\rightarrow p_{i+1},q_i\rightarrow q_{i+1}\),且 \((p_i-q_i)(p_{i+1}-q_{i+1})<0\),则称这两条路径在第 \(i\) 层产生了一个交点。一个路径方案的交点个数为两两不同路径的交点之和。
求有偶数个交点的路径方案数比有奇数个交点的路径方案数多多少个。\(n_1\le 10,k\le 100,m\le n^2\)。
Solution
首先考虑 \(k=2\) 的情况,此时可以将一个路径方案看作一个排列 \(\{p_i\}\)。那么交点个数就是这个排列的逆序对数量,因此答案即为逆序对为奇数的路径方案-逆序对为偶数的路径方案,这非常类似行列式的形式,事实上它就是邻接矩阵的行列式。
对于 \(k\) 较大的情况,由于 \(n_1=n_k\),所以一个路径方案依然可以看作一个排列 \(\{p_i\}\),表示第一层的第 \(i\) 个点最终到达的是第 \(k\) 层的 \(p_i\) 个点。如果 \(\{p_i\}\) 存在一个逆序对 \(i<j,p_i>p_j\),那么 \(i,j\) 点的路径中,它们每相交一次,大小关系就会交换,因此它们一定相交奇数次。所以,交点数的奇偶性依然等于 \(\{p_i\}\) 的逆序对数量的奇偶性!
既然如此,那么此时我们就不需要再关心中间层的情况,可以转化为一个 \(k=2\) 的情况,新图中点 \(i\) 到点 \(j\) 的路径数量可以通过在原图上直接 \(dp\) 完成,最终得到的新图邻接矩阵的行列式就是答案,复杂度 \(\mathcal O(n^3k)\)。
Code
#include<bits/stdc++.h>
using namespace std;
const int N=210,mod=998244353;
int T,n,k,a[N],sz[N];
int c[N][N],d[N][N];
inline int add(int x,int y){return (x+y>=mod)?x+y-mod:x+y;}
inline int dec(int x,int y){return (x-y<0)?x-y+mod:x-y;}
inline int ksm(int x,int y){
int ret=1;
for(;y;y>>=1,x=1ll*x*x%mod) if(y&1) ret=1ll*ret*x%mod;
return ret;
}
inline int del(int n){
int ans=1;
for(int i=1;i<=n;++i){
if(!c[i][i]){
for(int j=i+1;j<=n;++j)
if(c[j][i]){swap(c[i],c[j]);ans=dec(0,ans);break;}
}
if(!c[i][i]) return 0;
ans=1ll*ans*c[i][i]%mod;
int iv=ksm(c[i][i],mod-2);
for(int j=i;j<=n;++j) c[i][j]=1ll*c[i][j]*iv%mod;
for(int j=i+1;j<=n;++j)
for(int k=n;k>=i;--k)
c[j][k]=dec(c[j][k],1ll*c[i][k]*c[j][i]%mod);
}
return ans;
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&k);
for(int i=1;i<=k;++i) scanf("%d",&a[i]);
for(int i=1;i<k;++i) scanf("%d",&sz[i]);
for(int i=1;i<=a[1];++i)
for(int j=1;j<=a[1];++j)
c[i][j]=i==j?1:0;
for(int i=1;i<k;++i){
for(int j=1,u,v;j<=sz[i];++j){
scanf("%d%d",&u,&v);
for(int k=1;k<=a[1];++k)
d[k][v]=add(d[k][v],c[k][u]);
}
for(int j=1;j<=a[1];++j)
for(int k=1;k<=a[i+1];++k)
c[j][k]=d[j][k],d[j][k]=0;
}
printf("%d\n",del(a[1]));
}
return 0;
}
D1T3 庆典
Description
一张 \(n\) 个点 \(m\) 条边的有向图,图中边满足一个特殊性质:若 \(x\) 能到达 \(z\),\(y\) 能到达 \(z\),那么一定有 \(x\) 能到达 \(y\) 或者 \(y\) 到达 \(x\)。
现在有 \(q\) 次询问,每次加入 \(k\) 条边,给定两点 \(u,v\),询问新图中所有能被 \(u\) 到 \(v\) 的路径经过的点有多少个。新加入的边不一定满足性质,询问之间独立。
\(n,q\le 3\times 10^5,k\le 2\)。
Solution
首先考虑暴力怎么做,可以直接从 \(u,v\) 出发,分别沿正向边/反向边走,为经过的所有点打标记,最终两个点都能经过的点就可以计入答案。于是对这张图缩点成若干强连通分量一定不会影响答案。
缩点后,强连通分量之间显然依然满足题目中的特殊性质,因此若 \(x,y\) 都有到 \(z\) 的边,那么一定有 \(x\) 能到达 \(y\) 或 \(y\) 能到达 \(x\);如果 \(x\) 能到达 \(y\) 那么我们删去 \(x\) 到 \(z\) 的边,不影响图的连通性,\(y\) 能到达 \(z\) 同理。删去这些边后,原图就变成了一个外向树。
在外向树上做时可以大力讨论新加入的边是什么类型,但在 \(k=2\) 时太过繁琐。考虑对新加入的边的端点以及 \(u,v\) 建立虚树,接下来就只需要在虚树上使用类似暴力的做法,跑两边 \(dfs\) 就行了。唯一的区别在于暴力只用考虑点是否被覆盖,而在虚树上每条边都对应中原图上的许多点,因此还需要考虑边是否被覆盖。
最终复杂度为 \(\mathcal O(qk\log n)\)。
Code
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+20;
namespace iobuff{
const int LEN=1000000;
char in[LEN+5],out[LEN+5];
char *pin=in,*pout=out,*ed=in,*eout=out+LEN;
inline char gc(void){
return pin==ed&&(ed=(pin=in)+fread(in,1,LEN,stdin),ed==in)?EOF:*pin++;
}
inline void pc(char c){
pout==eout&&(fwrite(out,1,LEN,stdout),pout=out);
(*pout++)=c;
}
inline void flush(){fwrite(out,1,pout-out,stdout),pout=out;}
template<typename T> inline void read(T &x){
static int f;
static char c;
c=gc(),f=1,x=0;
while(c<'0'||c>'9') f=(c=='-'?-1:1),c=gc();
while(c>='0'&&c<='9') x=10*x+c-'0',c=gc();
x*=f;
}
template<typename T> inline void putint(T x,char div){
static char s[15];
static int top;
top=0;
x<0?pc('-'),x=-x:0;
while(x) s[top++]=x%10,x/=10;
!top?pc('0'),0:0;
while(top--) pc(s[top]+'0');
pc(div);
}
}
using namespace iobuff;
int n,m,q,k,first[N],cnt,dp[N],vis[N],l,r;
struct node{
int u,v,nxt,f;
}e[N<<2];
inline void add(int u,int v){e[++cnt].v=v;e[cnt].u=u;e[cnt].f=1;e[cnt].nxt=first[u];first[u]=cnt;}
int tot,dfn[N],ins[N],low[N],scc[N],stk[N],top,sum,siz[N],de[N];
vector<int> to[N],ru[N];
inline void tarjan(int u){
dfn[u]=low[u]=++tot;
stk[++top]=u;ins[u]=1;
for(int i=first[u];i;i=e[i].nxt){
int v=e[i].v;
if(!e[i].f) continue;
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}else if(ins[v]) low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
int t;++sum;siz[sum]=0;
do{
t=stk[top--];ins[t]=0;
scc[t]=sum;siz[sum]++;
}while(t!=u);
}
}
inline void work(int s,int t,int tp){
memset(dfn+1,0,sizeof(int)*(n));tot=0;top=0;sum=0;
memset(low+1,0,sizeof(int)*(n));
for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i);
for(int i=1;i<=sum;++i) to[i].clear(),ru[i].clear();
for(int i=1;i<=m;++i){
int u=e[i].u,v=e[i].v;
if(scc[u]!=scc[v]) to[scc[u]].push_back(scc[v]),ru[scc[v]].push_back(scc[u]);
}
for(int i=cnt-k+1;i<=cnt;++i){
int u=e[i].u,v=e[i].v;
if(scc[u]!=scc[v]) to[scc[u]].push_back(scc[v]),ru[scc[v]].push_back(scc[u]);
}
}
inline void rebuild(){
for(int i=1;i<=sum;++i) to[i].clear();
for(int u=1;u<=sum;++u){
int now=sum+1;
for(int i:ru[u]) now=min(now,i);
if(now!=sum+1) to[now].push_back(u);
}
for(int i=1;i<=sum;++i) ru[i].clear();
for(int i=1;i<=sum;++i)
for(int v:to[i]) ru[v].push_back(i);
}
namespace sub{
int dftot,dep[N],in[N],out[N],st[N<<2][21],pa[N],tmp[N],euler[N<<2],lg[N<<2];
inline void dfs(int u,int f,int x){
euler[++dftot]=u;in[u]=dftot;dep[u]=dep[f]+1;
tmp[u]=tmp[f]+siz[u];pa[u]=f;
for(int v:to[u]) dfs(v,u,x),euler[++dftot]=u;
out[u]=dftot;
}
inline bool pd(int u,int v){return in[u]<=in[v]&&out[v]<=out[u];}
inline int calc(int s,int t){
if(pd(s,t)) return tmp[t]-tmp[pa[s]];
else return 0;
}
inline int gmin(int x,int y){return dep[x]<dep[y]?x:y;}
inline void buildst(){
lg[0]=-1;for(int i=1;i<=dftot;++i)lg[i]=lg[i>>1]+1;
for(int i=1;i<=dftot;++i) st[i][0]=euler[i];
for(int j=1;(1<<j)<=dftot;++j)
for(int i=1;i+(1<<j)-1<=dftot;++i)
st[i][j]=gmin(st[i][j-1],st[i+(1<<j-1)][j-1]);
}
inline int LCA(int x,int y){
x=in[x];y=in[y];
if(x>y) swap(x,y);
int k=lg[y-x+1];
return gmin(st[x][k],st[y-(1<<k)+1][k]);
}
int tt,stk[7],p[N],top,vis[N],vn[N],pn[N],val[N];
int first[N],First[N],ct1[N],ct2[N];
vector<int> ve;
inline bool cmp(int x,int y){return in[x]<in[y];}
node d[N],g[N];
inline void link(int u,int v,int w){
if(!pn[u]) pn[u]=1,ve.push_back(u);
if(!pn[v]) pn[v]=1,ve.push_back(v);
++cnt;val[cnt]=(u==v)?w-siz[u]:w-siz[u]-siz[v];
d[cnt].v=v;d[cnt].nxt=first[u];first[u]=cnt;
g[cnt].v=u;g[cnt].nxt=First[v];First[v]=cnt;
}
inline void solve(int s,int tp){
if(vn[s]) return ;
vn[s]=1;
for(int i=(!tp?first[s]:First[s]);i;i=(!tp?d[i].nxt:g[i].nxt)){
int v=!tp?d[i].v:g[i].v;
vis[i]=1;
if(!vn[v]) solve(v,tp);
}
return ;
}
void main(){
for(int i=1;i<=sum;++i)
if(!ru[i].size()) dfs(i,0,i);
buildst();
for(tt=1;tt<=q;++tt){
int s,t;read(s);read(t);
s=scc[s];t=scc[t];
stk[0]=s;stk[1]=t;
int num=1;ve.clear();top=cnt=0;
for(int i=1,u,v;i<=k;++i){
read(u);read(v);
stk[++num]=scc[u],stk[++num]=scc[v];
link(scc[u],scc[v],siz[scc[u]]+siz[scc[v]]);
}
sort(stk,stk+num+1,cmp);
p[top=1]=stk[0];
for(int i=1;i<=num;++i){
int lca=LCA(p[top],stk[i]);
while(top>1&&dep[p[top-1]]>=dep[lca]) link(p[top-1],p[top],calc(p[top-1],p[top])),top--;
if(p[top]!=lca) link(lca,p[top],calc(lca,p[top])),p[top]=lca;
p[++top]=stk[i];
}
while(top>1) link(p[top-1],p[top],calc(p[top-1],p[top])),top--;
solve(s,0);
for(int i=1;i<=cnt;++i) if(vis[i]) ct1[i]=1,vis[i]=0;
for(int i:ve)
if(vn[i]) ct2[i]=1,vn[i]=0;
solve(t,1);
int ans=0;
for(int i=1;i<=cnt;++i){
if(vis[i]&&ct1[i]) ans+=val[i];
ct1[i]=vis[i]=0;
}
for(int i:ve){
if(vn[i]&&ct2[i]) ans+=siz[i];
vn[i]=pn[i]=ct2[i]=first[i]=First[i]=0;
}
putint(ans,'\n');
}
flush();
}
}
int main(){
read(n);read(m);read(q);read(k);
for(int i=1,u,v;i<=m;++i){
read(u);read(v);
add(u,v);
}
work(1,n,0);
if(m==n-1) sub::main();
else rebuild(),sub::main();
return 0;
}
D2T1 量子通信
Description
给出 \(n\) 个随机生成的 \(256\) 位二进制数 \(a_i\),有 \(m\) 次询问,给出一个 \(256\) 位二进制数 \(x\) 以及一个整数 \(k\),询问是否存在一个 \(a_i\) 与 \(x\) 不同的位数不超过 \(k\)。
\(n\le 4\times 10^5,m\le 1.2\times 10^5,k\le 15\)。
Solution
\(k\le 15\) 是题目中的一个关键性质。考虑将每个二进制数分为 \(16\) 段,那么根据鸽巢原理,符合条件的 \(a_i\) 至少有一段与 \(x\) 完全相同。
因此,只需要依次考虑 \(x\) 的 \(16\) 段,遍历这一段与 \(x\) 相同的二进制数,这样的二进制数期望下只有 \(\dfrac{n}{65536}\le 7\) 个,而检验只需要预处理出每个 \(16\) 位二进制数的 \(popcnt\) 即可做到对每个二进制数通过对 \(16\) 段分别计算完成检验,于是期望时间复杂度为 \(m\times 16\times 7\times 16\le 2\times 10^8\),复杂度可以接受。
Code
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
const int N=400010,M=65536;
int n,m,s[N][16],ct[M],a[16],lastans;
ull a1,a2;
char t[256];
vector<int> ve[16][M];
ull myRand(ull &k1,ull &k2){
ull k3=k1,k4=k2;
k1=k4;
k3^=(k3<<23);
k2=k3^k4^(k3>>17)^(k4>>26);
return k2+k4;
}
void gen(int n,ull a1,ull a2){
for(int i=1;i<=n;i++){
for(int j=0;j<256;j++){
int c=(myRand(a1,a2)&(1ull<<32))?1:0;
s[i][j>>4]=(s[i][j>>4]<<1)|c;
}
for(int j=0;j<16;++j)
ve[j][s[i][j]].push_back(i);
}
}
int main(){
scanf("%d%d%llu%llu",&n,&m,&a1,&a2);
gen(n,a1,a2);
ct[0]=0;
for(int i=1;i<M;++i) ct[i]=ct[i^(i&(-i))]+1;
lastans=0;
for(int i=1,k;i<=m;++i){
scanf("%s",t);scanf("%d",&k);
for(int j=0;j<16;++j) a[j]=0;
for(int j=0;j<64;++j){
int tmp=(t[j]>='0'&&t[j]<='9')?t[j]-'0':t[j]-'A'+10;
for(int k=3;k>=0;--k){
int c=((tmp>>k)&1)^lastans;
a[j>>2]=(a[j>>2]<<1)|c;
}
}
lastans=0;
for(int j=0;j<16&&!lastans;++j){
for(int x:ve[j][a[j]]){
int now=0;
for(int t=0;t<16&&now<=k;++t) now+=ct[a[t]^s[x][t]];
if(now<=k){lastans=1;break;}
}
}
printf("%d\n",lastans);
}
return 0;
}
D2T2 密码箱
Description
首先设第 \(k\) 项之后的部分得到的结果为分数 \(\dfrac{x}{y}\),那么加入第 \(k\) 项后得到 \(a_k+\dfrac{y}{x}=\dfrac{a_kx+y}{x}\),有 \(\gcd(a_kx+y,x)=\gcd(x,y)=1\),因此整个过程中分子分母始终互质,不用考虑约分的问题,于是可以直接维护一个分子分母组成的矩阵 \(\begin{bmatrix}x&y\end{bmatrix}\),那么加入第 \(k\) 项就等于乘上了矩阵
于是:
现在考虑 \(W,E\) 操作对矩阵的影响,:
-
\(W\) 操作将 \(a_i\) 变成 \(a_i+1\),有:
\[\begin{bmatrix} 1&k\\0&1\end{bmatrix} \begin{bmatrix}a_i&1\\1&0\end{bmatrix} =\begin{bmatrix}{a_i+k}&1\\1&0\end{bmatrix} \]因此,\(W\) 等价于在最右侧增加一个矩阵 \(\begin{bmatrix} 1&1\\0&1\end{bmatrix}\)。
-
\(E\) 操作,有两种情况,但是在 \(a_i=1\) 时用第一种情况,后两项变为 \(a_{i-1}+1,1\),用第二种情况得到 \(a_{i-1},0,1,1\),带入分数 \(\dfrac{x}{y}\) 可以发现二者得到了相同的结果。因此只需要考虑第二种情况,这等价于在最右侧增加三个矩阵:
\[\begin{bmatrix} 1&1\\1&0\end{bmatrix}\begin{bmatrix} 1&1\\1&0\end{bmatrix}\begin{bmatrix} 1&-1\\0&1\end{bmatrix}=\begin{bmatrix}2&{-1}\\1&0\end{bmatrix} \]于是 \(E\) 等价于在最右侧增加一个矩阵 \(\begin{bmatrix}2&{-1}\\1&0\end{bmatrix}\)。
至此,我们将两种操作分别等价为一个矩阵 \(A_i\),记 \(A_i\) \(flip\) 后变为矩阵 \(B_i\),只需要用文艺平衡树维护 \(A_i,B_i\) 的乘积即可。
Code
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10,mod=998244353;
int n,q,a[N];
char s[N];
inline int add(int x,int y){return (x+y>=mod)?x+y-mod:x+y;}
struct matrix{
int c[2][2];
inline void init(int d){c[0][0]=c[1][1]=d;c[0][1]=c[1][0]=0;}
}W,E;
inline matrix operator *(const matrix &A,const matrix &B){
matrix ret;ret.init(0);
for(int i=0;i<2;++i)
for(int j=0;j<2;++j)
ret.c[i][j]=(1ll*A.c[i][0]*B.c[0][j]+1ll*A.c[i][1]*B.c[1][j])%mod;
return ret;
}
int rt;
namespace Treap{
mt19937 rnd(time(0));
matrix val[N][4],sum[N][4];
//0:A1->n 1:An->1 2:B1->n 3:Bn->1
int tot,ran[N],ls[N],rs[N],rev[N],fl[N],siz[N];
inline void pushup(int p){
siz[p]=siz[ls[p]]+siz[rs[p]]+1;
for(int i=0;i<4;++i){
sum[p][i]=val[p][i];
if(ls[p]){
if(i&1) sum[p][i]=sum[p][i]*sum[ls[p]][i];
else sum[p][i]=sum[ls[p]][i]*sum[p][i];
}
if(rs[p]){
if(i&1) sum[p][i]=sum[rs[p]][i]*sum[p][i];
else sum[p][i]=sum[p][i]*sum[rs[p]][i];
}
}
}
inline void rever(int p){
if(!p) return ;
rev[p]^=1;
swap(sum[p][0],sum[p][1]);swap(sum[p][2],sum[p][3]);
swap(ls[p],rs[p]);
}
inline void flip(int p){
if(!p) return ;
fl[p]^=1;
for(int i=0;i<2;++i)
swap(val[p][i],val[p][i+2]),swap(sum[p][i],sum[p][i+2]);
}
inline void pushdown(int p){
if(rev[p]){
rever(ls[p]);rever(rs[p]);
rev[p]=0;
}
if(fl[p]){
flip(ls[p]);flip(rs[p]);
fl[p]=0;
}
}
inline int newnode(int tp){
int p=++tot;ran[p]=rnd();siz[p]=1;
val[p][0]=val[p][1]=sum[p][0]=sum[p][1]=tp?W:E;
val[p][2]=val[p][3]=sum[p][2]=sum[p][3]=tp?E:W;
return p;
}
inline int merge(int p1,int p2){
if(!p1||!p2) return p1+p2;
pushdown(p1);pushdown(p2);
if(ran[p1]<ran[p2]){
rs[p1]=merge(rs[p1],p2);
pushup(p1);return p1;
}
else{
ls[p2]=merge(p1,ls[p2]);
pushup(p2);return p2;
}
}
inline void split(int p,int v,int &l,int &r){
if(!p){l=r=0;return ;}
pushdown(p);
if(siz[ls[p]]+1<=v){
l=p;
split(rs[p],v-siz[ls[p]]-1,rs[p],r);
}
else{
r=p;
split(ls[p],v,l,ls[p]);
}
pushup(p);
}
inline void Flip(int l,int r){
int ql,pos,qr;
split(rt,l-1,ql,qr);
split(qr,r-l+1,pos,qr);
flip(pos);
rt=merge(merge(ql,pos),qr);
}
inline void Reverse(int l,int r){
int ql,pos,qr;
split(rt,l-1,ql,qr);
split(qr,r-l+1,pos,qr);
rever(pos);
rt=merge(merge(ql,pos),qr);
}
}
using namespace Treap;
inline void solve(){
matrix now=sum[rt][1];
printf("%d %d\n",now.c[0][0],add(now.c[0][0],now.c[0][1]));
}
int main(){
scanf("%d%d",&n,&q);
scanf("%s",s+1);
W.c[0][0]=W.c[0][1]=W.c[1][1]=1;W.c[1][0]=0;
E.c[0][0]=2;E.c[0][1]=mod-1;E.c[1][0]=1;E.c[1][1]=0;
for(int i=1;i<=n;++i) rt=merge(rt,newnode(s[i]=='W'));
solve();
while(q--){
scanf("%s",s+1);
if(s[1]=='A'){
scanf("%s",s+1);
rt=merge(rt,newnode(s[1]=='W'));
}
else if(s[1]=='F'){
int l,r;scanf("%d%d",&l,&r);
Flip(l,r);
}
else{
int l,r;scanf("%d%d",&l,&r);
Reverse(l,r);
}
solve();
}
return 0;
}
D2T3 机器人游戏
咕咕咕