【题目】
CC
一棵高度为
n
n
n的满二叉树,节点数为
2
n
+
1
−
1
2^{n+1}-1
2n+1−1,用一个
[
0
,
2
n
−
1
]
[0,2^n-1]
[0,2n−1]的整数
X
X
X表示一条根到叶子的路径,从最高位开始若为
0
0
0则走左儿子否则走右儿子。
初始
X
=
0
X=0
X=0,仅访问根节点,要求支持两种操作:
- 将 X X X改成 ( X + 2 C ) mod 2 n (X+2^C)\text{ mod } 2^n (X+2C) mod 2n,然后从根出发走到 X X X对应叶子。
- 询问当前有多少个节点至少被访问过一次。
n , Q ≤ 1 0 5 n,Q\leq 10^5 n,Q≤105
【解题思路】
这个题似乎还挺有意思的。
首先考虑一次修改的贡献,实际上就是
n
−
n-
n−新的
X
X
X与之前插入所有数的
lcp
\text{lcp}
lcp的最大值。
实际上我们需要动态维护一个数和其前驱后继。
如何动态维护这个数?一个数实际上是一个 01 01 01序列,加上 2 C 2^C 2C实际上是对于一段区间的翻转(修改位置到修改位置左边第一个 0 0 0)。可以用可持久化线段树来维护所有数字。
如何比较两个数的大小?一个更 simple \text{simple} simple的问题是比较两个数是否相同,这个可以用哈希解决,类似 CLONEME \text{CLONEME} CLONEME那个题目,给每一位一个哈希权值即可。比较大小实际上就是看不同的位置对应是 0 0 0还是 1 1 1咯,反正线段树上都维护了。
什么,怎么维护翻转后的哈希值?随便区间全为 1 1 1的哈希和减一下就好了吧。
什么,懒得打标记?没关系我们可以一位一位暴力改,反正每次由 0 0 0变 1 1 1势能只 + 1 +1 +1,修改次数还是 O ( n ) O(n) O(n)的。
比较一次的复杂度就是 O ( n log n ) O(n\log n) O(nlogn)的了,再用一个 set \text{set} set维护一下所有数的顺序就行了。
复杂度
O
(
Q
log
2
n
)
O(Q\log ^2 n)
O(Qlog2n),自然溢出一点也不虚,*B出题人卡我自然溢出。
【参考代码】
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int N=1e5+10,mod=998244353;
ll pw[N];
namespace IO
{
int read()
{
int ret=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return ret;
}
int readop()
{
char c=getchar();
while(c!='?' && c!='!') c=getchar();
return c=='?'?1:0;
}
void write(ll x){if(x>9)write(x/10);putchar(x%10^48);}
void writeln(ll x){write(x);putchar('\n');}
}
using namespace IO;
namespace Data_Structure
{
int rt[N];
struct Segment//higher righter,lower lefter
{
#define ls t[x].lc
#define rs t[x].rc
int sz;
struct node
{
ll val;int lc,rc;
}t[N*40];
void build(int &x,int l,int r)
{
x=++sz;t[x].val=t[x].lc=t[x].rc=0;
if(l==r) return;
int mid=(l+r)>>1;
build(ls,l,mid);build(rs,mid+1,r);
}
void update(int &x,int y,int l,int r,int p,int v)
{
x=++sz;t[x]=t[y];
if(l==r){t[x].val=v;return;}
int mid=(l+r)>>1;
if(p<=mid) update(ls,t[y].lc,l,mid,p,v);
else update(rs,t[y].rc,mid+1,r,p,v);
t[x].val=(1ll*pw[mid-l+1]*t[rs].val+t[ls].val)%mod;
}
bool cmp(int x,int y,int l,int r)
{
if(t[x].val==t[y].val) return 0;
if(l==r) return t[x].val<t[y].val;
int mid=(l+r)>>1;
if(t[rs].val==t[t[y].rc].val) return cmp(ls,t[y].lc,l,mid);
else return cmp(rs,t[y].rc,mid+1,r);
}
int calc(int x,int y,int l,int r)
{
if(t[x].val==t[y].val) return r-l+1;
if(l==r) return 0;
int mid=(l+r)>>1;
if(t[rs].val==t[t[y].rc].val) return r-mid+calc(ls,t[y].lc,l,mid);
else return calc(rs,t[y].rc,mid+1,r);
}
#undef ls
#undef rs
}T;
}
using namespace Data_Structure;
namespace DreamLolita
{
int rt,n,Q,a[N];
ll ans;
struct data
{
int x;
data(int _x=0):x(_x){}
bool operator <(const data&rhs)const{return T.cmp(x,rhs.x,1,n);}
};
set<data>st;
void solve()
{
n=read();Q=read();
ans=0;T.sz=rt=0;T.build(rt,1,n);
st.clear();for(int i=1;i<=n;++i) a[i]=0;
for(int i=1;i<=Q;++i)
{
int op=readop();
if(op) writeln(ans+1);
else
{
int x=read()+1;
for(;a[x];++x) a[x]=0,T.update(rt,rt,1,n,x,0);
if(x<=n) a[x]=1,T.update(rt,rt,1,n,x,1);
if(st.empty()) ans=n,st.insert(data(rt));
else
{
data y=data(rt);
set<data>::iterator it=st.lower_bound(y);
if(it==st.end()) --it,ans+=(n-T.calc((*it).x,rt,1,n));
else
{
data z=*it;
if(it!=st.begin())
{
--it;data c=*it;
ans+=(n-max(T.calc(z.x,rt,1,n),T.calc(c.x,rt,1,n)));
}
else ans+=(n-T.calc(z.x,rt,1,n));
}
st.insert(y);
}
}
}
}
void solution()
{
pw[0]=1;for(int i=1;i<N;++i) pw[i]=pw[i-1]*2%mod;
for(int T=read();T;--T) solve();
}
}
int main()
{
#ifdef Durant_Lee
freopen("CC_WALKBT.in","r",stdin);
freopen("CC_WALKBT.out","w",stdout);
#endif
DreamLolita::solution();
return 0;
}