数据结构做题记录
\(\text{Conventions}\)
- 提到二进制的第几位时,全部为从右往左 \(0\text{-index}\)。
- 题目强制在线要求解码时,解码过程不会赘述,请在原题面查看。
- 在题意中提到有根树时,若无特殊说明,默认以 \(1\) 为根。
\(\text{Problems}\)
\(\text{Problem}1\text{ CF}878\text{D (}2900\text{)}\)
一开始有 \(k\) 个长度为 \(n\) 的非负整数序列 \(A_1,A_2,\cdots,A_k\)。
接下来有 \(q\) 次操作,有以下 \(3\) 种类型(设每次操作前有 \(tot\) 个序列):
- 给定 \(x\) 和 \(y\),执行 \(tot\leftarrow tot+1,A_{tot,i}\leftarrow \max\{A_{x,i},A_{y,i}\}\)。
- 给定 \(x\) 和 \(y\),执行 \(tot\leftarrow tot+1,A_{tot,i}\leftarrow \min\{A_{x,i},A_{y,i}\}\)。
- 给定 \(x\) 和 \(y\),询问 \(A_{x,y}\)。
保证 \(1\le n,q\le10^5,1\le k\le\textcolor{red}{12},0\le A_{x,y}\le10^9\)。
\(\text{Time Limit }4\text{s},\text{Memory Limit }1024\text{MB}\)。
$\text{Hint}$
考虑值域为 \(\{0,1\}\) 的情况,可以发现本质不同的列至多只有 \(2^k\) 个。
$\text{Tutorial}$
请先阅读 \(\text{Hint}\)。
考虑值域为 \(\{0,1\}\) 怎么做。
我们考虑构造两个数组,一个名为 \(check\),一个名为 \(S\)。
我们希望 \(A_{x,y}\) 为 \(1\),当且仅当 \(S_{x,check_{y}}\) 为 \(1\)。
考虑这么构造 \(check_y\):\(check_y\) 二进制下第 \(x-1\) 位为 \(A_{x,y}\)。
若第 \(y_1\) 列和第 \(y_2\) 列完全相同,则 \(check_{y_1}=check_{y_2}\)。
这样是满足我们的要求的,因为若两列本质相同,无论对它们怎么操作仍然相同。
由于我们至多维护 \(2^k\) 个本质不同的列,所以我们这么构造 \(S_x\):当且仅当 \(T\) 在二进制下第 \(x-1\) 位为 \(1\) 时 \(S_{x,T}\) 为 \(1\)。
可以发现,这样构造对于初始 \(k\) 个序列是满足 \(\text{check}\) 的需求的。
考虑如何新建序列。
对于取 \(\max\),可以发现把 \(S_x\) 和 \(S_y\) 全部取 \(\max\) 即可满足要求;取 \(\min\) 也是同理。
观察到当值域为 \(\{0,1\}\) 时 \(\max\) 相当于 \(\operatorname{or}\),\(\min\) 相当于 \(\operatorname{and}\)。
所以这个过程可以 \(\text{bitset}\) 优化。
现在考虑没有值域为 \(\{0,1\}\) 的限制怎么做。
可以发现,按列离散化把值域变为 \([0,k)\) 不影响答案,因此现在值域就变为 \([0,k)\)。
若我们把大于 \(val\) 的值设为 \(1\),小于等于 \(val\) 的值设为 \(0\),则上述做法可以判断 \(A_{x,y}\) 是否大于 \(val\)。
所以可以把原问题转化成 \(k\) 个子问题,第 \(0\le i<k\) 个子问题形如判断 \(A_{x,y}\) 是否大于 \(i\)。
时间复杂度 \(O(nk^2+k\cdot2^k)-O(\dfrac{q2^k}{w})-O(qk)\),空间复杂度 \(O(nk+\dfrac{(q+k)2^k}{w})\)。
$\text{Solution}$
代码中全部为 \(0\text{-index}\)。
#define forUP(i,a,b) for(int i=(a);i<(b);++i)
constexpr int N=1e5+10,K=12;
int n,k,q,op,x,y,A[K][N];
int tot,lsh[N][K],check[N*K];bitset<1<<K> S[N+K];
void solve(){
cin>>n>>k>>q;tot=k;
forUP(i,0,k)forUP(j,0,n)cin>>A[i][j],lsh[j][i]=A[i][j];
forUP(j,0,n){
sort(lsh[j],lsh[j]+k);
forUP(i,0,k){
A[i][j]=lower_bound(lsh[j],lsh[j]+k,A[i][j])-lsh[j];
forUP(x,0,A[i][j])check[j*k+x]|=1<<i;
}
}
forUP(i,0,k)forUP(T,0,1<<k)S[i][T]=T>>i&1;
while(q--){
cin>>op>>x>>y,--x,--y;
if(op==1)S[tot++]=S[x]|S[y];
if(op==2)S[tot++]=S[x]&S[y];
if(op==3){
int ans=0;for(;S[x][check[y*k+ans]];++ans);
cout<<lsh[y][ans]<<'\n';
}
}
}
\(\text{Problem}2\text{ CF}455\text{D (}2700\text{)}\)
给定一个长度为 \(n\) 的正整数序列 \(A\)。
接下来有 \(q\) 次操作,有以下 \(2\) 种类型:
- 给定 \(l,r\),将 \([l,r]\) 向右循环平移 \(1\) 位。
- 给定 \(l,r,val\),询问 \(\displaystyle\sum_{i=l}^r[A_i=val]\)。
本题强制在线。
保证 \(1\le n,q\le10^5,1\le A_i,val\le n,1\le l\le r\le n\)。
\(\text{Time Limit }4\text{s},\text{Memory Limit }256\text{MB}\)。
$\text{Hint}$
考虑对序列分块。
$\text{Tutorial}$
发现平衡树不太好维护要查询的东西,考虑对序列分块。
设块长为 \(S\),考虑对每个块都开一个桶和双端队列。
查询是简单的,散块直接暴力遍历双端队列统计,整块直接调用桶的信息。
接下来具体讲讲如何修改。
若 \(l\) 和 \(r\) 在同一块内,直接暴力循环位移;
否则暴力位移处理散块,用 push_front 和 pop_back 维护中间整块的双端队列,同时维护桶的信息。
单次修改和查询时间复杂度都为 \(O(S+\dfrac{n}{S})\),取 \(S=\sqrt{n}\) 可以得到最优复杂度 \(O(\sqrt{n})\)。
时间复杂度为 \(O(n)-O(q\sqrt{n})-O(q\sqrt{n})\),空间复杂度为 \(O(n\sqrt{n})\)。
$\text{Solution}$
#define forUp(i,a,b) for(int i=(a);i<=(b);++i)
#define forDown(i,a,b) for(int i=(a);i>=(b);--i)
#define pushb push_back
#define popb pop_back
#define pushf push_front
constexpr int N=1e5+10,B=400,TOT=250+10;
int n,q,A[N],op,l,r,val,ans;
int get(int x){return (x+ans-1)%n+1;}
int blng[N],L[TOT],R[TOT],cnt[TOT][N];deque<int> dq[TOT];
void build(){
L[0]=1-B;forUp(i,1,250){
L[i]=L[i-1]+B,R[i]=R[i-1]+B;
forUp(pos,L[i],R[i])blng[pos]=i;
}
R[blng[n]]=n;
forUp(pos,1,n){
++cnt[blng[pos]][A[pos]];
dq[blng[pos]].pushb(A[pos]);
}
}
void update(int l,int r){
int lb=blng[l],rb=blng[r],tmp;
if(lb==rb){
tmp=dq[lb][r-L[lb]];
forDown(pos,r,l+1)dq[lb][pos-L[lb]]=dq[lb][pos-1-L[lb]];
dq[lb][l-L[lb]]=tmp;
}
else{
tmp=dq[lb][R[lb]-L[lb]];
--cnt[lb][tmp],++cnt[lb][dq[rb][r-L[rb]]];
forDown(pos,R[lb],l+1)dq[lb][pos-L[lb]]=dq[lb][pos-1-L[lb]];
dq[lb][l-L[lb]]=dq[rb][r-L[rb]];
forUp(i,lb+1,rb-1){
--cnt[i][dq[i][R[i]-L[i]]],++cnt[i][tmp];
dq[i].pushf(tmp),tmp=dq[i][R[i]-L[i]+1],dq[i].popb();
}
--cnt[rb][dq[rb][r-L[rb]]],++cnt[rb][tmp];
forDown(pos,r,L[rb]+1)dq[rb][pos-L[rb]]=dq[rb][pos-1-L[rb]];
dq[rb][0]=tmp;
}
}
int ask(int l,int r,int val){
int lb=blng[l],rb=blng[r],ans=0;
if(lb==rb)forUp(pos,l,r)ans+=dq[lb][pos-L[lb]]==val;
else{
forUp(pos,l,R[lb])ans+=dq[lb][pos-L[lb]]==val;
forUp(pos,L[rb],r)ans+=dq[rb][pos-L[rb]]==val;
forUp(i,lb+1,rb-1)ans+=cnt[i][val];
}
return ans;
}
void solve(){
cin>>n;
forUp(i,1,n)cin>>A[i];
build();
cin>>q;
while(q--){
cin>>op;
if(op==1){
cin>>l>>r;l=get(l),r=get(r);if(l>r)swap(l,r);
update(l,r);
}
if(op==2){
cin>>l>>r>>val;l=get(l),r=get(r),val=get(val);if(l>r)swap(l,r);
cout<<(ans=ask(l,r,val))<<'\n';
}
}
}
\(\text{Problem}3\text{ CF}226\text{E (}2900\text{)}\)
给定一个大小为 \(n\) 的有根树(根节点不一定为 \(1\)),每个点最初都为白色。
接下来有 \(q\) 次操作,有以下 \(2\) 种类型(设当前操作为第 \(i\) 次操作,操作为 \(1\text{-index}\)):
- 给定 \(u\),把 \(u\) 染成黑色。保证该操作对每个点至多执行 \(1\) 次。
- 给定 \(u,v,k,t_i\),定义可以选择的点为在操作 \((t_i,i]\) 之间颜色没有变化的点。
询问在有向链 \(u\rightarrow v\) 上按顺序第 \(k\) 个可选择的点(有向链不包含端点),若不存在这样的点则回答 \(-1\)。
保证 \(2\le n,q\le10^5,1\le u,v,k\le n,u\neq v,0\le t_i\le i\)。
\(\text{Time Limit }4\text{s},\text{Memory Limit }256\text{MB}\)。
$\text{Hint}$
看到路径查询,考虑重链剖分;
看到时间戳,考虑主席树。
$\text{Tutorial}$
请先阅读 \(\text{Hint}\)。
可以发现颜色变化的点的数量就是黑点数量之差。
考虑对树进行重链剖分,再套个主席树,维护一段 \(\text{dfn}\) 区间有多少个黑点。
修改就是简单的单点修改。
查询的话稍有点复杂,接下来将详细阐述。
依旧是跳重链。
如果跳的是 \(u\) 所在的重链,判断答案是否在这条链上,若是则直接二分找到,否则将 \(k\) 减去这条重链的贡献。
如果跳的是 \(v\) 所在的重链,则把这条重链压到一个栈里,等到 \(u\) 所在的重链全部处理完再处理。
需要注意的是 \(u\) 和 \(v\) 不算在路径内,需要在代码里特殊处理。
同时存在按 \(\text{dfn}\) 从小到大和从大到小两种顺序进行的二分。
具体可见 \(\text{Solution}\)。
$\text{Solution}$
#define forUp(i,a,b) for(int i=(a);i<=(b);++i)
#define forG(u,v) for(int __i=head[u],v=to[__i];__i;__i=nxt[__i],v=to[__i])
constexpr int N=1e5+10,M=N<<5;
int n,q,op,u,v,k,t;
int ecnt,head[N],to[N<<1],nxt[N<<1];
void addEdge(int u,int v){++ecnt,to[ecnt]=v,nxt[ecnt]=head[u],head[u]=ecnt;}
int fa[N],sz[N],dep[N],hson[N],dfnc,dfn[N],top[N],rk[N];
void DFS(int rt){
sz[rt]=1,hson[rt]=-1;
forG(rt,son){
dep[son]=dep[rt]+1;
DFS(son);
sz[rt]+=sz[son];
if(hson[rt]==-1||sz[son]>sz[hson[rt]])hson[rt]=son;
}
}
void DFN(int rt,int tp){
rk[dfn[rt]=++dfnc]=rt,top[rt]=tp;
if(hson[rt]==-1)return;
DFN(hson[rt],tp);
forG(rt,son)if(son!=hson[rt])DFN(son,son);
}
int tot,root[N],lson[M],rson[M],sum[M];
void update(int pre,int &cur,int l,int r,int pos){
cur=++tot;
lson[cur]=lson[pre],rson[cur]=rson[pre],sum[cur]=sum[pre]+1;
if(l==r)return;
int mid=l+r>>1;
if(pos<=mid)update(lson[pre],lson[cur],l,mid,pos);
else update(rson[pre],rson[cur],mid+1,r,pos);
}
int query(int rt,int l,int r,int L,int R){
if(!rt)return 0;
if(L<=l&&r<=R)return sum[rt];
int mid=l+r>>1,ans=0;
if(L<=mid)ans+=query(lson[rt],l,mid,L,R);
if(mid<R)ans+=query(rson[rt],mid+1,r,L,R);
return ans;
}
int lkth(int rtl,int rtr,int dfnl,int dfnr,int k){
int l=dfnl,r=dfnr;
while(l<r){
int mid=l+r>>1;
int sum=(mid-dfnl+1)-(query(rtr,1,n,dfnl,mid)-query(rtl,1,n,dfnl,mid));
if(sum<k)l=mid+1;
else r=mid;
}
return r;
}
int rkth(int rtl,int rtr,int dfnl,int dfnr,int k){
int l=dfnl,r=dfnr;
while(l<r){
int mid=l+r+1>>1;
int sum=(dfnr-mid+1)-(query(rtr,1,n,mid,dfnr)-query(rtl,1,n,mid,dfnr));
if(sum>=k)l=mid;
else r=mid-1;
}
return r;
}
void add(int i,int u){update(root[i-1],root[i],1,n,dfn[u]);}
int ask(int i,int u,int v,int k,int t){
root[i]=root[i-1];
int U=u,V=v;stack<array<int,2>> stk;
while(top[u]!=top[v]){
if(dep[top[u]]>=dep[top[v]]){
if(u==U)u=fa[u];
else{
int sum=(dep[u]-dep[top[u]]+1)-(query(root[i],1,n,dfn[top[u]],dfn[u])-query(root[t],1,n,dfn[top[u]],dfn[u]));
if(k<=sum)return rk[rkth(root[t],root[i],dfn[top[u]],dfn[u],k)];
k-=sum,u=fa[top[u]];
}
}
else{
if(v==V)v=fa[v];
else{
stk.push({top[v],v});
v=fa[top[v]];
}
}
}
if(dep[u]>dep[v]){
if(u==U)u=fa[u];
if(v==V)v=hson[v];
int sum=(dep[u]-dep[v]+1)-(query(root[i],1,n,dfn[v],dfn[u])-query(root[t],1,n,dfn[v],dfn[u]));
if(k<=sum)return rk[rkth(root[t],root[i],dfn[v],dfn[u],k)];
k-=sum;
}
else{
if(u==U)u=hson[u];
if(v==V)v=fa[v];
stk.push({u,v});
}
while(!stk.empty()){
auto [u,v]=stk.top();stk.pop();
int sum=(dep[v]-dep[u]+1)-(query(root[i],1,n,dfn[u],dfn[v])-query(root[t],1,n,dfn[u],dfn[v]));
if(k<=sum)return rk[lkth(root[t],root[i],dfn[u],dfn[v],k)];
k-=sum;
}
return -1;
}
void solve(){
cin>>n;
forUp(i,1,n)cin>>fa[i],addEdge(fa[i],i);
DFS(0);DFN(0,0);++n;
cin>>q;
forUp(i,1,q){
cin>>op;
if(op==1)cin>>u,add(i,u);
if(op==2)cin>>u>>v>>k>>t,cout<<ask(i,u,v,k,t)<<'\n';
}
}
\(\text{Problem}4\text{ CF}633\text{G (}2800\text{)}\)
给定一个正整数 \(V\) 和一棵大小为 \(n\) 的有根树,每个点有初始点权 \(A_i\)。
接下来有 \(q\) 次操作,有以下 \(2\) 种类型:
- 给定 \(u\) 和自然数 \(val\),对于 \(\forall v\in\operatorname{subtree}(u)\),执行 \(A_v\leftarrow A_v+val\)。
- 给定 \(u\),求有多少个满足条件的质数 \(P\):\(P<V,\exists v\in\operatorname{subtree}(u),k\in\mathbb N,A_v=P+V\times k\)。
保证 \(1\le n,q\le10^5,1\le V\le\textcolor{red}{1000},1\le u\le n,0\le A_i,val\le10^9\)。
\(\text{Time Limit }\textcolor{red}{4\text{s}},\text{Memory Limit }256\text{MB}\)。
$\text{Hint}$
条件显然可以转化为 \(P<V,\exists v\in\operatorname{subtree}(u),A_v\equiv P\pmod V\)。
可以发现,我们只关心一个区间内一个数在模 \(V\) 的意义下是否出现过。
$\text{Tutorial}$
请先阅读 \(\text{Hint}\)。
考虑用 \(\text{bitset}\) 维护区间是否有一个数在模 \(V\) 的意义下是否出现过。
可以发现,修改相当于对 \(\text{bitset}\) 循环移位,查询相当于求区间的 \(\text{bitset}\)。
在线段树上打 \(\text{lazytag}\) 可以轻松解决。
时间复杂度 \(O(\dfrac{qV\log n}{w})\),空间复杂度 \(O(\dfrac{nV}{w})\)。
由于值域较小且时限较松所以可以过。
$\text{Solution}$
#define forUp(i,a,b) for(int i=(a);i<=(b);++i)
#define forG(u,v) for(int __i=head[u],v=to[__i];__i;__i=nxt[__i],v=to[__i])
constexpr int N=1e5+10,M=1000;
int n,V,A[N],u,v,q,op,val;
int ecnt,head[N],to[N<<1],nxt[N<<1];
void addEdge(int u,int v){
++ecnt,to[ecnt]=v,nxt[ecnt]=head[u],head[u]=ecnt;
++ecnt,to[ecnt]=u,nxt[ecnt]=head[v],head[v]=ecnt;
}
int DFN,bg[N],ed[N],rk[N];
void dfs(int rt,int fa){
rk[bg[rt]=++DFN]=rt;
forG(rt,son)if(son!=fa)dfs(son,rt);
ed[rt]=DFN;
}
int pcnt,P[M];bitset<M> isp;
void sieve(int V){
forUP(i,2,V)isp[i]=1;
forUP(i,2,V){
if(isp[i])P[++pcnt]=i;
for(int j=1;j<=pcnt&&i*P[j]<V;++j){
isp[i*P[j]]=0;
if(i%P[j]==0)break;
}
}
}
bitset<M> bucket[N<<2];int add[N<<2];
void pushup(int rt){bucket[rt]=bucket[rt<<1]|bucket[rt<<1|1];}
void modify(int rt,int tag){
(add[rt]+=tag)%=V;
bucket[rt]=(bucket[rt]<<tag)|(bucket[rt]>>V-tag);
}
void pushdown(int rt){
if(add[rt]){
modify(rt<<1,add[rt]);modify(rt<<1|1,add[rt]);
add[rt]=0;
}
}
void build(int rt,int l,int r){
if(l==r){
bucket[rt][A[rk[l]]%V]=1;
return;
}
int mid=l+r>>1;
build(rt<<1,l,mid);build(rt<<1|1,mid+1,r);
pushup(rt);
}
void update(int rt,int l,int r,int L,int R,int val){
if(L<=l&&r<=R)return modify(rt,val);
int mid=l+r>>1;
pushdown(rt);
if(L<=mid)update(rt<<1,l,mid,L,R,val);
if(mid<R)update(rt<<1|1,mid+1,r,L,R,val);
pushup(rt);
}
bitset<M> query(int rt,int l,int r,int L,int R){
if(L<=l&&r<=R)return bucket[rt];
int mid=l+r>>1;
pushdown(rt);
if(R<=mid)return query(rt<<1,l,mid,L,R);
else if(mid<L)return query(rt<<1|1,mid+1,r,L,R);
else return query(rt<<1,l,mid,L,R)|query(rt<<1|1,mid+1,r,L,R);
}
void solve(){
cin>>n>>V;
forUp(i,1,n)cin>>A[i];
forUP(i,1,n)cin>>u>>v,addEdge(u,v);
dfs(1,0);
build(1,1,n);
sieve(V);
cin>>q;
while(q--){
cin>>op;
if(op==1){
cin>>u>>val;
update(1,1,n,bg[u],ed[u],val%V);
}
if(op==2){
cin>>u;
cout<<(query(1,1,n,bg[u],ed[u])&isp).count()<<'\n';
}
}
}
\(\text{Problem}5\text{ CF}1422\text{F (}2700\text{)}\)
给定一个长度为 \(n\) 的正整数序列 \(A\)。
接下来有 \(q\) 次询问,每次给定 \(l,r\),询问 \(\operatorname{lcm}(A_l\sim A_r)\bmod (10^9+7)\)。
本题强制在线。
保证 \(1\le n,q\le10^5,1\le A_i\le2\times10^5,1\le l\le r\le n\)。
\(\text{Time Limit }3\text{s},\text{Memory Limit }512\text{MB}\)。
$\text{Hints}$
$\text{Hint}1$
考虑分解质因数,对每个质数的贡献分开考虑。
$\text{Hint}2$
请尝试解决可以离线的原问题,再考虑怎么变成在线。
$\text{Hint}3$
考虑在序列末尾加一个数对答案的贡献。
$\text{Tutorial}$
请先阅读 \(\text{Hints}\)。
考虑离线做法。
考虑对 \(r\) 扫描线,维护每个 \(l\) 的答案。
对于每个质数,我们考虑从右往左的增量,并在对应的位置设为增量,则询问就变成求区间积。
我们用单调栈维护每个质数增量出现的位置和次数(注意记录的不是增量),往右扫的时候维护单调栈和线段树即可。
现在要求强制在线,把线段树换成可持久化线段树即可。
时间复杂度 \(O(n\log n\log V)-O(q\log n)\),空间复杂度 \(O(n\log n\log V)\)。
$\text{Solution}$
#define forUp(i,a,b) for(int i=(a);i<=(b);++i)
constexpr int N=1e5+10,V=2e5+10,M=300*1e5+10,mod=1e9+7;
int n,A[N],q,l,r,ans;
int get(int x){return (ans+x)%n+1;}
int tot,root[N],lson[M],rson[M],mul[M];
void build(int &rt,int l,int r){
mul[rt=++tot]=1;
if(l==r)return;
int mid=l+r>>1;
build(lson[rt],l,mid);build(rson[rt],mid+1,r);
}
void update(int pre,int &cur,int l,int r,int pos,int val){
cur=++tot;
lson[cur]=lson[pre],rson[cur]=rson[pre],mul[cur]=1ll*mul[pre]*val%mod;
if(l==r)return;
int mid=l+r>>1;
if(pos<=mid)update(lson[pre],lson[cur],l,mid,pos,val);
else update(rson[pre],rson[cur],mid+1,r,pos,val);
}
int query(int rt,int l,int r,int L,int R){
if(!rt||R<l||r<L)return 1;
if(L<=l&&r<=R)return mul[rt];
int mid=l+r>>1,ans=1;
if(L<=mid)ans=1ll*ans*query(lson[rt],l,mid,L,R)%mod;
if(mid<R)ans=1ll*ans*query(rson[rt],mid+1,r,L,R)%mod;
return ans;
}
int inv[V],minp[V];
void sieve(int n=V-10){
minp[1]=1;
forUp(i,2,n)if(!minp[i])for(int j=i;j<=n;j+=i)minp[j]=i;
inv[0]=inv[1]=1;
forUp(i,2,n)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
}
stack<array<int,2>> stk[V];
void solve(){
cin>>n;
forUp(i,1,n)cin>>A[i];
sieve();
build(root[0],1,n);
forUp(i,1,n){
root[i]=root[i-1];
while(A[i]>1){
int p=minp[A[i]],k=1;
while(A[i]%p==0)A[i]/=p,k*=p;int lst=1;
while(!stk[p].empty()&&stk[p].top()[1]<k){
auto [pos,val]=stk[p].top();stk[p].pop();
update(root[i],root[i],1,n,pos,1ll*inv[val]*lst%mod);
lst=val;
}
if(!stk[p].empty())update(root[i],root[i],1,n,stk[p].top()[0],1ll*inv[k]*lst%mod);
update(root[i],root[i],1,n,i,k);
stk[p].push({i,k});
}
}
cin>>q;
while(q--){
cin>>l>>r;l=get(l),r=get(r);if(l>r)swap(l,r);
cout<<(ans=query(root[r],1,n,l,r))<<'\n';
}
}
\(\text{Problem}6\text{ CF}1749\text{F (}2800\text{)}\)
给定一棵大小为 \(n\) 的有根树,最初每个点的点权为 \(0\)。
接下来有 \(q\) 次操作,有以下 \(2\) 种类型:
- 给定 \(u\),询问 \(u\) 的点权。
- 给定 \(u,v,val,d\),将满足到链 \(u\rightarrow v\) 的距离 \(\le d\) 所有点的点权加上 \(val\)。
点到链的距离定义为该点到该链上任意一点的距离的最小值。
保证 \(2\le n\le2\times10^5,1\le q\le2\times10^5,1\le u,v\le n,1\le val\le1000,0\le d\le\textcolor{red}{20}\)。
\(\text{Time Limit }4\text{s},\text{Memory Limit }512\text{MB}\)。
$\text{Hint}$
发现 \(d\) 很小,直接跳祖先复杂度可以接受,考虑把贡献挂到点的祖先上。
$\text{Tutorial}$
我们令 \(f_{k,u}\) 为 \(u\) 子树内到 \(u\) 的距离为 \(k\) 的点的加法标记。
则查询就是对 \(u\) 的 \(k\) 级祖先 \(v\) 求 \(\displaystyle\sum f_{k,v}\)。
现在考虑如何修改,令 \(lca=\operatorname{LCA}(u,v)\)。
稍微讨论下可以发现,修改可以转化为 \(3\) 次对到根链的 \(f_d\) 加,以及对 \(lca\) 的祖先共做 \(O(d)\) 次单点加。
到根链加可以变成单点加子树和,单点加对询问的贡献可以另外计算。
时间复杂度 \(O(q(\log n+d))-O(qd\log n)\),空间复杂度 \(O(nd)\)。
$\text{Solution}$
#define forUp(i,a,b) for(int i=(a);i<=(b);++i)
#define forG(u,v) for(int __i=head[u],v=to[__i];__i;__i=nxt[__i],v=to[__i])
constexpr int N=2e5+10,D=21;
int n,u,v,q,op,d,val;
int ecnt,head[N],to[N<<1],nxt[N<<1];
void addEdge(int u,int v){
++ecnt,to[ecnt]=v,nxt[ecnt]=head[u],head[u]=ecnt;
++ecnt,to[ecnt]=u,nxt[ecnt]=head[v],head[v]=ecnt;
}
int DFN,fa[20][N],dep[N],bg[N],ed[N];
void dfs(int rt){
bg[rt]=++DFN,dep[rt]=dep[fa[0][rt]]+1;
for(int i=0;fa[i][rt];++i)fa[i+1][rt]=fa[i][fa[i][rt]];
forG(rt,son)if(son!=fa[0][rt])fa[0][son]=rt,dfs(son);
ed[rt]=DFN;
}
int LCA(int u,int v){
if(dep[u]<dep[v])swap(u,v);
forDown(i,19,0)if(dep[fa[i][u]]>=dep[v])u=fa[i][u];
if(u==v)return u;
forDown(i,19,0)if(fa[i][u]!=fa[i][v])u=fa[i][u],v=fa[i][v];
return fa[0][u];
}
struct fenwick{
int c[N];
void add(int pos,int val){for(;pos<=n;pos+=pos&-pos)c[pos]+=val;}
int ask(int pos){int ans=0;for(;pos;pos-=pos&-pos)ans+=c[pos];return ans;}
int ask(int l,int r){return ask(r)-ask(l-1);}
}f[D];int g[D][N];
void solve(){
cin>>n;
forUP(i,1,n)cin>>u>>v,addEdge(u,v);
dfs(1);
cin>>q;
while(q--){
cin>>op;
if(op==1){
cin>>u;int ans=0;
for(int k=0;k<D&&u;u=fa[0][u],++k)ans+=f[k].ask(bg[u],ed[u])+g[k][u];
cout<<ans<<'\n';
}
if(op==2){
cin>>u>>v>>val>>d;int lca=LCA(u,v);
f[d].add(bg[u],val);f[d].add(bg[v],val);f[d].add(bg[lca],-2*val);
for(int k=0;k<=d;++k,lca=fa[0][lca]){
g[d-k][lca]+=val;
if(k<d)g[d-k-1][lca]+=val;
if(lca==1){
for(k+=2;k<=d;++k)g[d-k][lca]+=val;
break;
}
}
}
}
}
\(\text{Problem}7\text{ CF}1010\text{E (}2700\text{)}\)
给定 \(3\) 个正整数 \(X,Y,Z\),接下来提到的所有点和长方体都在长方体 \(([1,X],[1,Y],[1,Z])\) 内。
有一个隐藏的长方体 \(C\),给定 \(n_1\) 个在 \(C\) 内的点和 \(n_2\) 个不在 \(C\) 内的点。
注意给出的信息可能有矛盾,该情况下不用继续回答询问。
接下来有 \(q\) 次询问,每次询问给定一个点,询问该点是否:
- 一定在 \(C\) 内。
- 一定不在 \(C\) 内。
- 都有可能。
保证 \(1\le X,Y,Z,n_1,q\le10^5,0\le n_2\le10^5\)。
\(\text{Time Limit }2\text{s},\text{Memory Limit }256\text{MB}\)。
$\text{Hint}$
$1$ 是易于判断的,重点在于 $2,3$。考虑一个点如果在 \(C\) 内会怎么样。
$\text{Tutorial}$
请先阅读 \(\text{Hint}\)。
如果一个点在 \(C\) 内会得出矛盾,则它一定不在 \(C\) 内,否则不确定。
因此问题就转化为三维数点,\(\text{K-D Tree}\) 即可。
时间复杂度 \(O(n)-O(qn^{\frac{2}{3}})\),空间复杂度 \(O(n)\)。
$\text{Solution}$
#define forUp(i,a,b) for(int i=(a);i<=(b);++i)
#define forUP(i,a,b) for(int i=(a);i<(b);++i)
constexpr int N=1e5+10,K=3;
int n1,n2,q;array<int,K> lim,pos,L,R;
struct KDTnode{
array<int,K> pos,L,R;
int lson,rson;
}KDT[N];int root;
void pushup(int rt){
forUP(k,0,K){
KDT[rt].L[k]=KDT[rt].R[k]=KDT[rt].pos[k];
if(KDT[rt].lson)chkMin(KDT[rt].L[k],KDT[KDT[rt].lson].L[k]),chkMax(KDT[rt].R[k],KDT[KDT[rt].lson].R[k]);
if(KDT[rt].rson)chkMin(KDT[rt].L[k],KDT[KDT[rt].rson].L[k]),chkMax(KDT[rt].R[k],KDT[KDT[rt].rson].R[k]);
}
}
int build(int l,int r,int dep=0){
if(l>r)return 0;
int mid=l+r>>1,rt=mid;nth_element(KDT+l,KDT+mid,KDT+r,[dep](const KDTnode &lhs,const KDTnode &rhs)->bool{return lhs.pos[dep]<rhs.pos[dep];});
if(l<mid)KDT[rt].lson=build(l,mid-1,(dep+1)%K);
if(mid<r)KDT[rt].rson=build(mid+1,r,(dep+1)%K);
pushup(rt);
return rt;
}
void query(int rt,array<int,K> L,array<int,K> R,bool &ans){
if(ans)return;
if(!rt)return;
bool in=true;forUP(k,0,K)in&=L[k]<=KDT[rt].pos[k]&&KDT[rt].pos[k]<=R[k];
if(in){ans=true;return;}
forUP(k,0,K)if(KDT[rt].R[k]<L[k]||R[k]<KDT[rt].L[k])return;
query(KDT[rt].lson,L,R,ans);query(KDT[rt].rson,L,R,ans);
}
void solve(){
cin>>lim[0]>>lim[1]>>lim[2]>>n1>>n2>>q;
L=lim,R={1,1,1};
forUp(i,1,n1){
forUP(k,0,K)cin>>pos[k];
forUP(k,0,K)chkMin(L[k],pos[k]),chkMax(R[k],pos[k]);
}
forUp(i,1,n2){
forUP(k,0,K)cin>>KDT[i].pos[k];
bool in=true;
forUP(k,0,K)in&=L[k]<=KDT[i].pos[k]&&KDT[i].pos[k]<=R[k];
if(in){
cout<<"INCORRECT\n";
return;
}
}
cout<<"CORRECT\n";root=build(1,n2);
while(q--){
forUP(k,0,K)cin>>pos[k];
bool in=true;
forUP(k,0,K)in&=L[k]<=pos[k]&&pos[k]<=R[k];
if(in){
cout<<"OPEN\n";
continue;
}
array<int,K> tL,tR;forUP(k,0,K)tL[k]=min(L[k],pos[k]),tR[k]=max(R[k],pos[k]);
query(root,tL,tR,in);
if(in)cout<<"CLOSED\n";
else cout<<"UNKNOWN\n";
}
}
\(\text{Problem}8\text{ CF}576\text{E (}3300\text{)}\)
给定一个包含 \(n\) 个点 \(m\) 条边的无向图 \(G\) 和 \(k\) 种颜色,每条边可以不染或染成颜色 \(1\sim k\)。
最初所有边都未染色。
接下来有 \(q\) 次操作,每次操作给定一条在 \(G\) 上的边 \(edge\) 和颜色 \(c\):
- 若把 \(edge\) 染成颜色 \(c\) 后,对于相同颜色所形成的子图仍为二分图,则保留该操作,并回答操作成功。
- 否则,该操作不生效,回答操作失败。
保证 \(2\le n\le5\times10^5,1\le m,q\le5\times10^5,1\le k\le\textcolor{red}{50},1\le c\le k\)。
\(\text{Time Limit }6\text{s},\text{Memory Limit }\textcolor{red}{600\text{MB}}\)。
$\text{Hint}$
发现和加边删边判断二分图类似,考虑线段树分治。
$\text{Tutorial}$
请先阅读 \(\text{Hint}\)。
对于一条边,前后两次染色的时间为 \(x,y\),则染色的时间区间为 \((x,y)\)。
颜色有两种可能,一种是染上去了,一种是维持原样。
我们只需要分治到叶子后判断是否满足条件,据此改变边的颜色即可。
由于 \(k\) 比较小,可以直接开 \(k\) 个可撤销并查集维护。
时间复杂度 \(O(m\log n\log q)\),空间复杂度 \(O(nk+m\log q)\)。
$\text{Solution}$
#define forUp(i,a,b) for(int i=(a);i<=(b);++i)
constexpr int N=5e5+10,V=50+1;
int n,m,k,q,u[N],v[N],eid[N],c[N];
int fa[V][N<<1],sz[V][N<<1],tp,stkU[N<<1],stkV[N<<1],stkC[N<<1];
void init(int k,int n){forUp(i,1,k)forUp(j,1,n<<1)sz[i][fa[i][j]=j]=1;}
int find(int id,int x){return x==fa[id][x]?x:find(id,fa[id][x]);}
void merge(int id,int x,int y){
x=find(id,x),y=find(id,y);
if(x==y)return;
if(sz[id][x]<sz[id][y])swap(x,y);
fa[id][y]=x,sz[id][x]+=sz[id][y];
stkU[++tp]=x,stkV[tp]=y,stkC[tp]=id;
}
void undo(){
int x=stkU[tp],y=stkV[tp],id=stkC[tp--];
fa[id][y]=y,sz[id][x]-=sz[id][y];
}
int col[N],lst[N];vector<int> edges[N<<2];
void insert(int rt,int l,int r,int L,int R,int id){
if(L<=l&&r<=R)return edges[rt].pushb(id);
int mid=l+r>>1;
if(L<=mid)insert(rt<<1,l,mid,L,R,id);
if(mid<R)insert(rt<<1|1,mid+1,r,L,R,id);
}
void dfs(int rt,int l,int r){
int lsttp=tp;
for(int id:edges[rt]){
int x=u[eid[id]],y=v[eid[id]],w=c[id];
merge(w,find(w,x),find(w,y+n));
merge(w,find(w,x+n),find(w,y));
}
if(l==r){
int w=c[l],x=u[eid[l]],y=v[eid[l]];
if(find(w,x)==find(w,y))cout<<"NO\n",c[l]=col[eid[l]];
else cout<<"YES\n",col[eid[l]]=c[l];
}
else{
int mid=l+r>>1;
dfs(rt<<1,l,mid);dfs(rt<<1|1,mid+1,r);
}
while(lsttp!=tp)undo();
}
void solve(){
cin>>n>>m>>k>>q;init(k,n);
forUp(i,1,m)cin>>u[i]>>v[i];
forUp(i,1,q)cin>>eid[i]>>c[i],lst[eid[i]]=q+1;
forDown(i,q,1){
if(i+1<lst[eid[i]])insert(1,1,q,i+1,lst[eid[i]]-1,i);
lst[eid[i]]=i;
}
dfs(1,1,q);
}
\(\text{Problem}9\text{ CF}1491\text{H (}3400\text{)}\)
给定一棵大小为 \(n\) 的有根树的父亲序列 \(fa\)。
接下来有 \(q\) 次操作,有以下 \(2\) 种类型:
- 给定 \(l,r,val\),对于 \(\forall l\le i\le r\),执行 \(fa_i\leftarrow \max(1,fa_i-val)\)。
- 给定 \(u,v\),询问 \(\operatorname{LCA}(u,v)\)。
保证 \(2\le n,q\le10^5,1\le fa_i<\textcolor{red}{i},2\le l\le r\le n,\textcolor{red}{1}\le val\le10^5,1\le u,v\le n\)。
\(\text{Time Limit }1.5\text{s},\text{Memory Limit }256\text{MB}\)。
$\text{Hint}$
仿照弹飞绵羊的分块做法,考虑维护一个点第一个与其不在同一块的祖先 \(pre\)。
$\text{Tutorial}$
请先阅读 \(\text{Hint}\)。
设分块的块长为 \(S\)。
考虑如何做修改。
可以发现,一个整块最多减 \(O(S)\) 次 \(fa_i\) 就和 \(i\) 不在同一块,此时整块打加法标记即可。
散块可以暴力减后暴力重构,整块在可以打标记之前也可以直接暴力重构。
修改的总体时间复杂度是 \(O(nS+q(S+\dfrac{n}{S}))\)。
考虑如何查询 \(\operatorname{LCA}(u,v)\)。
考虑和树链剖分求 \(\operatorname{LCA}\) 类似的方式:
- 若 \(u\) 和 \(v\) 不在同一块,则所在块编号大的点跳到 \(pre\)。
- 否则若 \(pre_u\neq pre_v\),则 \(u\) 和 \(v\) 同时跳到 \(pre\)。
- 否则编号大的点跳到 \(fa\),直到相等为止。
可以发现跳 \(pre\) 的操作至多有 \(O(\dfrac{n}{S})\) 次,跳 \(fa\) 的操作至多有 \(O(S)\) 次。
所以单次查询的时间复杂度为 \(O(S+\dfrac{n}{S})\)。
当 \(S=\sqrt{n}\) 时取到最优复杂度。
时间复杂度 \(O(n)-O((n+q)\sqrt{n})-O(q\sqrt{n})\),空间复杂度 \(O(n)\)。
$\text{Solution}$
#define forUp(i,a,b) for(int i=(a);i<=(b);++i)
constexpr int N=1e5+10,TOT=318+10;
int n,q,op,l,r,val,pos1,pos2;
int B,tot,blng[N],L[TOT],R[TOT],fa[N],pre[N],cnt[TOT],add[TOT];
void refresh(int bid){
forUp(pos,L[bid],R[bid])fa[pos]=max(1,fa[pos]+add[bid]);add[bid]=0;
forUp(pos,L[bid],R[bid]){
if(fa[pos]<L[bid])pre[pos]=fa[pos];
else pre[pos]=pre[fa[pos]];
}
}
void build(){
B=sqrt(n),tot=(n-1)/B+1;
L[0]=1-B;forUp(i,1,tot){
L[i]=L[i-1]+B,R[i]=min(R[i-1]+B,n);
forUp(pos,L[i],R[i])blng[pos]=i;
}
forUp(i,1,tot)refresh(i);
}
void update(int l,int r,int val){
int lb=blng[l],rb=blng[r];
if(lb==rb){
forUp(pos,l,r)fa[pos]=max(1,fa[pos]+val);
refresh(lb);
}
else{
forUp(pos,l,R[lb])fa[pos]=max(1,fa[pos]+val);refresh(lb);
forUp(pos,L[rb],r)fa[pos]=max(1,fa[pos]+val);refresh(rb);
forUp(bid,lb+1,rb-1){
++cnt[bid],add[bid]=max(-n,add[bid]+val);
if(cnt[bid]<=B)refresh(bid);
}
}
}
int getpre(int pos){return max(1,pre[pos]+add[blng[pos]]);}
int LCA(int pos1,int pos2){
while(true){
if(blng[pos1]<blng[pos2])swap(pos1,pos2);
if(blng[pos1]!=blng[pos2])pos1=getpre(pos1);
else if(getpre(pos1)!=getpre(pos2))pos1=getpre(pos1),pos2=getpre(pos2);
else break;
}
while(pos1!=pos2){
if(pos1<pos2)swap(pos1,pos2);
pos1=max(1,fa[pos1]+add[blng[pos1]]);
}
return pos1;
}
void solve(){
cin>>n>>q;
forUp(i,2,n)cin>>fa[i];
build();
while(q--){
cin>>op;
if(op==1){
cin>>l>>r>>val;
update(l,r,-val);
}
if(op==2){
cin>>pos1>>pos2;
cout<<LCA(pos1,pos2)<<'\n';
}
}
}
\(\text{Problem}10\text{ CF}575\text{I (}2800\text{)}\)
给定一个正整数 \(n\),接下来提到的点和三角形都在矩形 \(([1,n],[1,n])\) 内。
接下来有 \(q\) 次操作,有以下 \(2\) 种类型:
- 给定 \(dir,x,y,len\),要求在腰长为 \(len\),直角的坐标为 \((x,y)\) 的等腰直角三角形内(包含边界)的点的权值加 \(1\)。
当 \(dir=1\) 时,直角在左下角;当 \(dir=2\) 时,直角在左上角;
当 \(dir=3\) 时,直角在右下角;当 \(dir=4\) 时,直角在右上角。 - 给定 \(x,y\),询问点 \((x,y)\) 的权值。
保证 \(1\le n\le\textcolor{red}{5000},1\le q\le10^5,1\le dir\le 4\),所有三角形的顶点和点的坐标都在 \([1,n]\) 内。
\(\text{Time Limit }1.5\text{s},\text{Memory Limit }512\text{MB}\)。
$\text{Hints}$
$\text{Hint}1$
注意到 \(4\) 中情况是对称的,考虑如何处理 \(dir=1\) 的情况。
$\text{Hint}2$
发现修改操作本质的限制为 \(x'+y'\in[x+y,x+y+len],x'\in[x,n],y'\in[y,n]\)。
考虑拆成几个部分,使得限制更为简单。
$\text{Tutorial}$
请先阅读 \(\text{Hints}\)。
注意到题目所求,即蓝色部分,等于红色部分减黄色部分。
注意到红色部分满足 \(x'+y'\in[x+y,x+y+len],y'\in[y,n]\),黄色部分满足 \(x'+y'\in[x+y,x+y+len],x'\in[1,x)\)。
考虑开两棵二维树状数组,分别维护 \((x+y,x)\) 和 \((x+y,y)\),则变成 \(4\text{-side}\) 矩形加单点查。
差分后就变成单点加 \(2\text{-side}\) 矩形查。
时间复杂度 \(O(q\log^2n)\),空间复杂度 \(O(n^2)\)。
$\text{Solution}$
#define forUp(i,a,b) for(int i=(a);i<=(b);++i)
constexpr int Q=1e5+10;
int n,q,op,dir[Q],x[Q],y[Q],len[Q],ans[Q];
struct fenwick{
int n,m;
vector<vector<int>> BIT;
void resize(int _n,int _m){
n=_n,m=_m;
BIT.resize(n+1);
forUp(i,0,n)BIT[i].resize(m+1);
forUp(i,0,n)forUp(j,0,m)BIT[i][j]=0;
}
void add(int X,int Y,int val){for(int x=X;x<=n;x+=x&-x)for(int y=Y;y<=m;y+=y&-y)BIT[x][y]+=val;}
void add(int U,int D,int L,int R,int val){
add(D+1,R+1,val);
add(D+1,L,-val);
add(U,R+1,-val);
add(U,L,val);
}
int ask(int X,int Y){int ans=0;for(int x=X;x;x-=x&-x)for(int y=Y;y;y-=y&-y)ans+=BIT[x][y];return ans;}
}Tx,Ty;
void resize(int n,int m){Tx.resize(n,m);Ty.resize(n,m);}
void add(int x,int y,int len,int val){
Tx.add(x+y,x+y+len,1,x-1,val);
Ty.add(x+y,x+y+len,y,n,val);
}
int ask(int x,int y){return Ty.ask(x+y,y)-Tx.ask(x+y,x);}
void solve(){
cin>>n>>q;
forUp(i,1,q){
cin>>op;
if(op==1)cin>>dir[i]>>x[i]>>y[i]>>len[i];
if(op==2)dir[i]=5,cin>>x[i]>>y[i];
}
forUp(op,1,4){
resize(n<<1,n);
forUp(i,1,q)if(dir[i]==op||dir[i]==5){
int tx,ty;
if(op==1)tx=x[i],ty=y[i];
if(op==2)tx=x[i],ty=n-y[i]+1;
if(op==3)tx=n-x[i]+1,ty=y[i];
if(op==4)tx=n-x[i]+1,ty=n-y[i]+1;
if(dir[i]==op)add(tx,ty,len[i],1);
if(dir[i]==5)ans[i]+=ask(tx,ty);
}
}
forUp(i,1,q)if(dir[i]==5)cout<<ans[i]<<'\n';
}
\(\text{Problem}11\text{ CF}1667\text{E (}2900\text{)}\)
给定一个长度为 \(n\) 的排列 \(A\)。
定义区间 \([l,r]\) 为好的,当且仅当 \(\exists (i,j),l\le i<j\le r,A_i\times A_j=\max\{A_l,A_{l+1},\cdots,A_r\}\)。
接下来有 \(q\) 次询问,每次给定 \(l,r\),询问区间 \([l,r]\) 有多少个好的子区间 \([x,y]\)。
保证 \(1\le n\le2\times10^5,1\le q\le10^6,1\le l\le r\le n\)。
\(\text{Time Limit }4\text{s},\text{Memory Limit }1024\text{MB}\)。
$\text{Hints}$
$\text{Hint1}$
套路考虑单调栈预处理 \(A_i\) 能成为最大值的极大区间 \([L_i,R_i]\)。
$\text{Solution}$
#define forUp(i,a,b) for(int i=(a);i<=(b);++i)
#define forDown(i,a,b) for(int i=(a);i>=(b);--i)
template<class _Tp>void chkMax(_Tp &x,const _Tp &y){x<y?x=y:0;}
template<class _Tp>void chkMin(_Tp &x,const _Tp &y){x>y?x=y:0;}
constexpr int N=2e5+10,Q=1e6+10;
int n,q,A[N],l,r;int64 ans[Q];
int pos[N],tp,stk[N],L[N],R[N];vector<array<int,3>> vecL[N],vecR[N];vector<int> fac[N];
int64 sum[N<<2],add[N<<2];
void pushup(int rt){sum[rt]=sum[rt<<1]+sum[rt<<1|1];}
void modify(int rt,int l,int r,int64 tag){
add[rt]+=tag;
sum[rt]+=(r-l+1)*tag;
}
void pushdown(int rt,int l,int r){
if(add[rt]){
int mid=l+r>>1;
modify(rt<<1,l,mid,add[rt]);modify(rt<<1|1,mid+1,r,add[rt]);
add[rt]=0;
}
}
void build(int rt,int l,int r){
sum[rt]=add[rt]=0;
if(l==r)return;
int mid=l+r>>1;
build(rt<<1,l,mid);build(rt<<1|1,mid+1,r);
}
void update(int rt,int l,int r,int L,int R,int val){
if(L<=l&&r<=R)return modify(rt,l,r,val);
int mid=l+r>>1;
pushdown(rt,l,r);
if(L<=mid)update(rt<<1,l,mid,L,R,val);
if(mid<R)update(rt<<1|1,mid+1,r,L,R,val);
pushup(rt);
}
int64 query(int rt,int l,int r,int L,int R){
if(L<=l&&r<=R)return sum[rt];
int mid=l+r>>1;int64 ans=0;
pushdown(rt,l,r);
if(L<=mid)ans+=query(rt<<1,l,mid,L,R);
if(mid<R)ans+=query(rt<<1|1,mid+1,r,L,R);
return ans;
}
void solve(){
cin>>n>>q;
forUp(i,1,n)cin>>A[i];
forUp(i,1,n)pos[A[i]]=i;
forUp(i,1,n)L[i]=1,R[i]=n;
forUp(i,1,n){
while(tp&&A[stk[tp]]<A[i])R[stk[tp--]]=i-1;
L[i]=stk[tp]+1,stk[++tp]=i;
}
forUp(i,1,n)for(int j=i;j<=n;j+=i)fac[j].pushb(i);
forUp(i,1,n){
if(R[i]-i<i-L[i]){
l=L[i],r=L[i]-1;
for(auto x:fac[A[i]]){
int y=A[i]/x;if(x>=y)break;
if(L[i]<=pos[x]&&pos[x]<=i&&L[i]<=pos[y]&&pos[y]<=i)chkMax(r,min(pos[x],pos[y]));
}
forUp(j,i,R[i]){
if(A[i]%A[j]==0){
int x=A[j],y=A[i]/A[j];
if(x!=y&&L[i]<=pos[y]&&pos[y]<=j)chkMax(r,pos[y]),chkMin(r,i);
}
if(l<=r)vecR[j].pushb({0,l,r});
}
}
else{
l=R[i]+1,r=R[i];
for(auto x:fac[A[i]]){
int y=A[i]/x;if(x>=y)continue;
if(i<=pos[x]&&pos[x]<=R[i]&&i<=pos[y]&&pos[y]<=R[i])chkMin(l,max(pos[x],pos[y]));
}
forDown(j,i,L[i]){
if(A[i]%A[j]==0){
int x=A[j],y=A[i]/A[j];
if(x!=y&&j<=pos[y]&&pos[y]<=R[i])chkMin(l,pos[y]),chkMax(l,i);
}
if(l<=r)vecL[j].pushb({0,l,r});
}
}
}
forUp(i,1,q){
cin>>l>>r;
vecL[l].pushb({i,l,r});
vecR[r].pushb({i,l,r});
}
build(1,1,n);
forDown(i,n,1)for(auto [qid,l,r]:vecL[i]){
if(qid==0)update(1,1,n,l,r,1);
else ans[qid]+=query(1,1,n,l,r);
}
build(1,1,n);
forUp(i,1,n)for(auto [qid,l,r]:vecR[i]){
if(qid==0)update(1,1,n,l,r,1);
else ans[qid]+=query(1,1,n,l,r);
}
forUp(i,1,q)cout<<ans[i]<<'\n';
}
\(\text{Problem}12\text{ CF}1635\text{F (}2800\text{)}\)
$\text{Solution}$
#define forUp(i,a,b) for(int i=(a);i<=(b);++i)
#define forDown(i,a,b) for(int i=(a);i>=(b);--i)
constexpr int N=3e5+10;
int n,q,x[N],w[N],l,r;int64 ans[N];
int tp,stk[N],L[N],R[N];vector<array<int64,3>> vec[N];
int64 c[N];
void upd(int pos,int64 val){for(pos=n-pos+1;pos<=n;pos+=pos&-pos)chkMin(c[pos],val);}
int64 ask(int pos){int64 ans=INF64;for(pos=n-pos+1;pos;pos-=pos&-pos)chkMin(ans,c[pos]);return ans;}
void solve(){
cin>>n>>q;
forUp(i,1,n)cin>>x[i]>>w[i];
forUp(i,1,n){
while(tp&&w[stk[tp]]>=w[i])R[stk[tp--]]=i;
stk[++tp]=i;
}
while(tp)R[stk[tp--]]=n+1;
forDown(i,n,1){
while(tp&&w[stk[tp]]>=w[i])L[stk[tp--]]=i;
stk[++tp]=i;
}
while(tp)L[stk[tp--]]=0;
forUp(i,1,n){
if(R[i]!=n+1)vec[R[i]].pushb({0,i,1ll*(x[R[i]]-x[i])*(w[i]+w[R[i]])});
if(L[i]!=0)vec[i].pushb({0,L[i],1ll*(x[i]-x[L[i]])*(w[L[i]]+w[i])});
}
forUp(i,1,q){
cin>>l>>r;
vec[r].pushb({i,l,0});
}
memset(c,INF,sizeof(c));
forUp(i,1,n)for(auto [op,pos,val]:vec[i]){
if(op==0)upd(pos,val);
else ans[op]=ask(pos);
}
forUp(i,1,q)cout<<ans[i]<<'\n';
}
\(\text{Problem}13\text{ CF}2045\text{J (}2600\text{)}\)
$\text{Solution}$
#define forDown(i,a,b) for(int i=(a);i>=(b);--i)
constexpr int LEN=6e6+10;
int n,m,A,X,mn=1<<30;int64 ans;
struct Trie{
int tot,to[LEN][2],sz[LEN];
Trie(){tot=1;memset(to,0,sizeof(to));memset(sz,0,sizeof(sz));}
void insert(int val){
int cur=1;
forDown(i,29,0){
int c=val>>i&1;
if(!to[cur][c])to[cur][c]=++tot;
cur=to[cur][c],++sz[cur];
}
}
int ask(int val){
int cur=1,ans=0;
forDown(i,29,0){
int c=val>>i&1;
if(to[cur][c])cur=to[cur][c];
else cur=to[cur][c^1],ans|=1<<i;
}
return ans;
}
int query(int val,int lim){
int cur=1,ans=0;
forDown(i,29,0){
int c1=val>>i&1,c2=lim>>i&1;
if(c2)ans+=sz[to[cur][c1]],cur=to[cur][c1^1];
else cur=to[cur][c1];
}
ans+=sz[cur];
return ans;
}
}trie[2];
void solve(){
cin>>n>>m;
while(n--){
cin>>A;
chkMin(mn,trie[0].ask(A));
trie[0].insert(A);
}
while(m--){
cin>>X;
ans+=trie[1].query(X,mn);
trie[1].insert(X);
}
cout<<ans;
}
\(\text{Problem}14\text{ CF}1578\text{B (}2800\text{)}\)
$\text{Solution}$
#define forDown(i,a,b) for(int i=(a);i>=(b);--i)
template<class _Tp>void chkMax(_Tp &x,const _Tp &y){x<y?x=y:0;}
template<class _Tp>void chkMin(_Tp &x,const _Tp &y){x>y?x=y:0;}
constexpr int N=2e5+10;
int n,q,op,x,y;
struct DSU{
int fa[N],sz[N],L[N],R[N];
void init(){forUp(i,1,n)sz[fa[i]=L[i]=R[i]=i]=1;}
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
void merge(int x,int y){
x=find(x),y=find(y);
if(sz[x]<sz[y])swap(x,y);
fa[y]=x,sz[x]+=sz[y],chkMin(L[x],L[y]),chkMax(R[x],R[y]);
}
int getL(int x){return L[find(x)];}
int getR(int x){return R[find(x)];}
}dsu;
struct segtree{
int mn[N<<2],add[N<<2];
segtree(){memset(mn,0,sizeof(mn));memset(add,0,sizeof(add));}
void pushup(int rt){mn[rt]=min(mn[rt<<1],mn[rt<<1|1]);}
void modify(int rt,int tag){mn[rt]+=tag;add[rt]+=tag;}
void pushdown(int rt){if(add[rt]){modify(rt<<1,add[rt]);modify(rt<<1|1,add[rt]);add[rt]=0;}}
void update(int L,int R,int val,int rt=1,int l=1,int r=n){
if(L>R)return;
if(L<=l&&r<=R)return modify(rt,val);
int mid=l+r>>1;
pushdown(rt);
if(L<=mid)update(L,R,val,rt<<1,l,mid);
if(mid<R)update(L,R,val,rt<<1|1,mid+1,r);
pushup(rt);
}
int query(int pos,int rt=1,int l=1,int r=n){
if(l==r)return mn[rt];
int mid=l+r>>1;
pushdown(rt);
if(pos<=mid)return query(pos,rt<<1,l,mid);
else return query(pos,rt<<1|1,mid+1,r);
}
int findR(int pos,int lim,int rt=1,int l=1,int r=n){
if(pos<=l){
if(mn[rt]>=lim)return n+1;
if(l==r)return l;
int mid=l+r>>1;
pushdown(rt);
if(mn[rt<<1]<lim)return findR(pos,lim,rt<<1,l,mid);
else return findR(pos,lim,rt<<1|1,mid+1,r);
}
int mid=l+r>>1,ans=n+1;
pushdown(rt);
if(pos<=mid)ans=findR(pos,lim,rt<<1,l,mid);
if(ans==n+1)ans=findR(pos,lim,rt<<1|1,mid+1,r);
return ans;
}
int findL(int pos,int lim,int rt=1,int l=1,int r=n){
if(r<=pos){
if(mn[rt]>=lim)return 0;
if(l==r)return l;
int mid=l+r>>1;
pushdown(rt);
if(mn[rt<<1|1]<lim)return findL(pos,lim,rt<<1|1,mid+1,r);
else return findL(pos,lim,rt<<1,l,mid);
}
int mid=l+r>>1,ans=0;
pushdown(rt);
if(mid<pos)ans=findL(pos,lim,rt<<1|1,mid+1,r);
if(ans==0)ans=findL(pos,lim,rt<<1,l,mid);
return ans;
}
}T;
void merge(int x,int y){
int Lx=dsu.getL(x),Rx=dsu.getR(x),Ly=dsu.getL(y),Ry=dsu.getR(y);
if(Rx<Ly)T.update(Rx+1,Ly-1,1);
else T.update(max(Lx,Ly),min(Rx,Ry),-1);
dsu.merge(x,y);
}
void solve(){
cin>>n>>q;dsu.init();
while(q--){
cin>>op>>x>>y;
if(op==1){
if(x>y)swap(x,y);
int hx=T.query(x),hy=T.query(y);
while(dsu.find(x)!=dsu.find(y)){
if(hx>hy)merge(x,T.findR(x,hx)),--hx;
else if(hx<hy)merge(T.findL(y,hy),y),--hy;
else{
int pos=T.findR(x,hx);
if(pos<y)merge(x,pos),--hx;
else merge(x,y);
}
}
}
if(op==2)cout<<(dsu.find(x)==dsu.find(y));
}
}

浙公网安备 33010602011771号