钱易数据结构杂题选讲
【IOI2021】registers
【IOI2021】dungeons
考虑一个关键性质:打死一个怪以后,英雄的体力值会增长怪物的血量,也就是说,如果英雄打死了一个和自己势均力敌的怪物,那么其血量会成倍增长。
发现了这个性质之后,我们考虑对权值分层,即设第 \(i\) 层表示血量区间 \([2^i,2^{i+1})\),假设现在英雄血量位于第 \(i\) 层表示的区间,那显然血量 \(<2^i\) 的怪都可以被英雄秒掉,而对于血量 \(≥2^i\) 的怪,我们不知道其是否能被英雄打败,因此我们考虑这样的策略:先假设它们都不能被英雄打败,这样每次英雄经过血量 \(<2^i\) 的房间就会获得 \(s_j\) 的血量并到达房间 \(w_i\),经过血量 \(≥2^i\) 的房间就会获得 \(p_j\) 的血量并到达房间 \(l_i\)。我们考虑对此做倍增,即设:
-
\(to[i][j][k]\) 表示在第 \(i\) 层,从 \(j\) 开始,每经过一个血量 \(<2^i\) 的怪都到达 \(w_i\),经过一个 \(≥2^i\) 的怪都到达 \(l_i\),这样走 \(2^k\) 步后会到达哪个房间。
-
\(sum[i][j][k]\) 表示在第 \(i\) 层,从 \(j\) 开始,每经过一个血量 \(<2^i\) 的怪都积累 \(s_i\) 的血量,经过一个 \(≥2^i\) 的怪都积累 \(p_i\) 的血量,这样走 \(2^k\) 步后血量会增加多少。
-
\(upp[i][j][k]\) 表示在第 \(i\) 层,从 \(j\) 开始,初始血量至多为多少,才能保证在接下来 \(2^k\) 步中打不败任何一个 \(≥2^i\) 的怪。
上面三个数组都可以在预处理时候求出。这样每次查询时,每进入一层,就倍增找到后面第一个可以打败的血量 \(≥2^i\) 的怪,根据上面的推论,打掉这个怪之后肯定会进入下一层,因此总复杂度 \(O((n+q)\log^2n)\),由于 \(n\) 较大,直接这样做会 \(\text{MLE}\)。
发现对于血量不在 \([2^i,2^{i+1})\) 之间的怪,操作是比较简单的,考虑将 \([2^i,2^{i+1})\) 之间的怪单独拿出来。
-
\(to[i][j]\) 表示在第 \(i\) 层,从 \(j\) 开始,遇到的第一个血量在 \([2^i,2^{i+1})\) 之间的怪。
-
\(sum[i][j]\) 表示在第 \(i\) 层,从 \(j\) 开始,遇到的第一个血量在 \([2^i,2^{i+1})\) 之间的怪时血量增加了多少。
-
\(pos[j][k]\) 表示从 \(j\) 开始,在当前层内跳 \(k\) 步会跳到哪里。
-
\(val[j][k]\) 表示从 \(j\) 开始,每经过一个血量小于当前层的怪都积累 \(s_i\) 的血量,否则积累 \(p_i\) 的血量,这样走 \(2^k\) 步后血量会增加多少。
-
\(upp[j][k]\) 表示从 \(j\) 开始,初始血量至多为多少,才能保证在接下来 \(2^k\) 步中打不败任何一个血量高于当前层的怪。
我们从小到大枚举层数,如果遇到第一个血量在 \([2^i,2^{i+1})\) 之间的怪时,英雄的血量已经超过了 \(2^{i+1}\) ,那么这一层的怪物就不会形成阻碍,可以按照下一层来处理。
否则,跳到这个点,然后在不超过当前层的地方倍增地向前跳,跳到第一个可以升到上一层的点即可,总时间复杂度仍然是 \(O((n+q)\log^2n)\) ,空间 \(O(n\log n)\) 。
点击查看代码
#include"dungeons.h"
#include<bits/stdc++.h>
using namespace std;
int n,rnd,lb,ub;
int s[400005],p[400005],w[400005],l[400005];
int nxt[25][400005],pos[25][400005];
long long sum[25][400005],val[25][400005],upp[25][400005];
bool vis[400005];
void dfs(int x){
if(vis[x])return ;
vis[x]=1;
int z=s[x]<lb?w[x]:l[x];
dfs(z);
nxt[rnd][x]=nxt[rnd][z];
sum[rnd][x]=sum[rnd][z]+(s[x]<lb?s[x]:p[x]);
}
void init(int n,vector<int> S,vector<int> P,vector<int> W,vector<int> L){
::n=n;
for(int i=1;i<=n;i++)s[i]=S[i-1];
for(int i=1;i<=n;i++)p[i]=P[i-1];
for(int i=1;i<=n;i++)w[i]=W[i-1]+1;
for(int i=1;i<=n;i++)l[i]=L[i-1]+1;
for(rnd=0;rnd<=24;rnd++){
lb=(1<<rnd);ub=2*lb-1;
for(int i=1;i<=n;i++)vis[i]=0;
vis[n+1]=1;nxt[rnd][n+1]=n+1;sum[rnd][n+1]=0;
for(int i=1;i<=n;i++){
if(lb<=s[i]&&s[i]<=ub)vis[i]=1,nxt[rnd][i]=i,sum[rnd][i]=0;
}
for(int i=1;i<=n;i++)dfs(i);
for(int i=1;i<=n;i++){
if(lb<=s[i]&&s[i]<=ub){
pos[0][i]=nxt[rnd][l[i]];val[0][i]=sum[rnd][l[i]]+p[i];
upp[0][i]=max(0ll,ub-val[0][i]);
if(upp[0][i]>=s[i])upp[0][i]=s[i]-1;
}
}
}
for(int i=1;i<=24;i++){
for(int j=1;j<=n;j++){
pos[i][j]=pos[i-1][pos[i-1][j]];
val[i][j]=val[i-1][j]+val[i-1][pos[i-1][j]];
upp[i][j]=min(upp[i-1][j],max(0ll,upp[i-1][pos[i-1][j]]-val[i-1][j]));
}
}
}
long long simulate(int x,int Z){
long long z=Z;++x;
for(int rnd=0;rnd<=24;++rnd){
int lb=(1<<rnd),ub=2*lb-1;
if(nxt[rnd][x]&&z+sum[rnd][x]<=ub){
z+=sum[rnd][x];x=nxt[rnd][x];
if(x==n+1)return z;
for(int i=24;i>=0;i--){
if(z<=upp[i][x]&&pos[i][x])z+=val[i][x],x=pos[i][x];
}
if(x==n+1)return z;
if(z<s[x])z+=p[x],x=l[x];
else z+=s[x],x=w[x];
if(x==n+1)return z;
}
}
z+=sum[24][x];
return z;
}
#671. 【UNR #5】诡异操作
首先可以想到一个 \(O(128×n+128×q\log n)\) 的做法,空间开不下,时间也过不去。
考虑这一做法在计算机中的储存方式,每一个节点维护区间内 \(128\) 个数位每位 \(1\) 的数量,每个数量用 \(1\) 个 \(32\) 位的 \(int\) 来储存,实际上有用的不超过 \(\log(r−l+1)\) 位。
容易发现这是一个 \(128×\log(r−l+1)\) 的 \(01\) 表,我们考虑沿对角线翻转它,复杂度就变成了 \(O(n\log n+q\log^2 n)\) 。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,q;
inline int read(){
int res=0;char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')res=10*res+c-'0',c=getchar();
return res;
}
typedef __uint128_t u128;
inline u128 read_u(){
static char buf[200];
scanf("%s",buf);u128 res=0;
for(int i=0;buf[i];i++)res=res<<4|(buf[i]<='9'?buf[i]-'0':buf[i]-'a'+10);
return res;
}
inline void output(u128 res){
if(res>=16)output(res/16);
putchar(res%16>=10?'a'+res%16-10:'0'+res%16);
}
u128 *vec[1200005],pool[2000005],*lim=pool;
int empty[1200005],cnt[1200005];
u128 a[300005],lazy[1200005];
inline void pushup(int x){
u128 flag=0;
for(int i=0;i<cnt[x];i++){
u128 a=(cnt[x<<1]>i?vec[x<<1][i]:0),b=(cnt[x<<1|1]>i?vec[x<<1|1][i]:0);
vec[x][i]=a^b^flag;
flag=((a^b)&flag)|(a&b);
}
empty[x]=(empty[x<<1]&empty[x<<1|1]);
}
inline void And(int x,u128 v){
if(empty[x])return ;
for(int i=0;i<cnt[x];i++){
vec[x][i]&=v;
}
lazy[x]&=v;
}
inline void pushdown(int i){
if(~lazy[i])And(i<<1,lazy[i]),And(i<<1|1,lazy[i]);
lazy[i]=-1;
}
void build(int l=1,int r=n,int i=1){
while((1<<cnt[i])<=r-l+1)cnt[i]++;
vec[i]=lim;lim+=cnt[i];lazy[i]=-1;
if(l==r){
vec[i][0]=a[l];empty[i]=!a[l];
return ;
}
int mid=(l+r)>>1;
build(l,mid,i<<1);build(mid+1,r,i<<1|1);
pushup(i);
}
void Div(int fr,int to,u128 v,int l=1,int r=n,int i=1){
if(fr>r||to<l||empty[i])return ;
if(l==r){
vec[i][0]/=v;empty[i]=(!vec[i][0]);
return ;
}
int mid=(l+r)>>1;pushdown(i);
Div(fr,to,v,l,mid,i<<1);Div(fr,to,v,mid+1,r,i<<1|1);
pushup(i);
}
void update(int fr,int to,u128 v,int l=1,int r=n,int i=1){
if(fr>r||to<l||empty[i])return ;
if(fr<=l&&to>=r){
And(i,v);return ;
}
int mid=(l+r)>>1;pushdown(i);
update(fr,to,v,l,mid,i<<1);update(fr,to,v,mid+1,r,i<<1|1);
pushup(i);
}
u128 query(int fr,int to,int l=1,int r=n,int i=1){
if(fr>r||to<l||empty[i])return 0;
if(fr<=l&&to>=r){
u128 res=0;
for(int j=0;j<cnt[i];j++)res+=(vec[i][j]<<j);
return res;
}
int mid=(l+r)>>1;pushdown(i);
return query(fr,to,l,mid,i<<1)+query(fr,to,mid+1,r,i<<1|1);
}
int main(){
n=read();q=read();
for(int i=1;i<=n;i++)a[i]=read_u();
build();
while(q--){
int op,l,r;u128 v;
op=read();l=read();r=read();
if(op==1){
v=read_u();
if(v!=1)Div(l,r,v);
}
if(op==2){
v=read_u();
update(l,r,v);
}
if(op==3)output(query(l,r)),puts("");
}
return 0;
}
#592. 新年的聚会
对于一张 \(m\) 条边的图,我们有办法将它的点集分为 \(O(\sqrt m)\) 个独立集。
我们可以通过至多 \(n\sqrt m\) 次询问暴力把这些独立集找出来,考虑两个独立集之间的连边,将较大独立集划分为两个部分,对于与较小独立集有连边的部分递归下去,这样我们就可以通过 \(\log n\) 次询问找出一条边,我们可以通过 \(m\log n\) 询问找出所有的边。
点击查看代码
#include<bits/stdc++.h>
#include "meeting.h"
using namespace std;
vector<pair<int,int> >edge;
vector<int> s[1005];
inline void conquer(int id1,int id2,int l1,int r1,int l2,int r2){
if(r1-l1>r2-l2){
swap(id1,id2);
swap(l1,l2);
swap(r1,r2);
}
if(l1==r1&&l2==r2){
edge.emplace_back(s[id1][l1],s[id2][l2]);
return ;
}
int mid=(l2+r2)>>1;
vector<int> A,B;
for(int i=l1;i<=r1;++i){
A.emplace_back(s[id1][i]);
B.emplace_back(s[id1][i]);
}
for(int i=l2;i<=mid;++i) A.emplace_back(s[id2][i]);
for(int i=mid+1;i<=r2;++i) B.emplace_back(s[id2][i]);
if(!meeting(A)) conquer(id1,id2,l1,r1,l2,mid);
if(!meeting(B)) conquer(id1,id2,l1,r1,mid+1,r2);
return ;
}
int cnt=0;
vector<pair<int,int> >solve(int n){
for(int i=0;i<n;++i){
bool mark=0;
for(int j=1;j<=cnt;++j){
s[j].emplace_back(i);
if(meeting(s[j])){
mark=1;
break;
}
s[j].pop_back();
}
if(!mark) s[++cnt].emplace_back(i);
}
for(int i=1;i<=cnt;++i){
for(int j=i+1;j<=cnt;++j){
int siz1=s[i].size(),siz2=s[j].size();
conquer(i,j,0,siz1-1,0,siz2-1);
}
}
return edge;
}
#604. 【UER #9】赶路
考虑分治,令 \(\text{solve(s,vec,t)}\) 表示构造一条从 \(s\) 点出发,经过 \(\text{vec}\) 中的点,到达 \(t\) 的合法路径。
我们只需从 \(\text{vec}\) 中随便选出一点 \(\text{mid}\) ,做向量 \(s\to \text{mid}\) ,将 \(\text{vec}\) 中与 \(t\) 在 \(s\to \text{mid}\) 同侧的放入 \(R\) 集合,否则放入 \(L\) 集合,有:
期望复杂度 \(O(n\log n)\),最劣复杂度 \(O(n^2)\) ,数据范围很小,没有明显区别。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int T;
int n;
struct node{
long long x,y;
node(long long _x=0,long long _y=0){
x=_x;y=_y;
}
inline node operator -(const node &b)const{
return node(x-b.x,y-b.y);
}
inline long long operator *(const node &b)const{
return x*b.y-y*b.x;
}
}a[505];
vector<int> Get(int s,vector<int> vec,int t){
if(vec.empty())return vec;
auto mid=vec.back();vec.pop_back();
long long tmp=(a[mid]-a[s])*(a[t]-a[s]);tmp/=abs(tmp);
vector<int> L,R;
for(auto it:vec){
if((a[mid]-a[s])*(a[it]-a[s])*tmp<0)L.push_back(it);
else R.push_back(it);
}
auto l=Get(s,L,mid),r=Get(mid,R,t);
vector<int> res;
for(auto it:l)res.push_back(it);
res.push_back(mid);
for(auto it:r)res.push_back(it);
return res;
}
inline void solve(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
int x,y;scanf("%d%d",&x,&y);
a[i]=node(x,y);
}
vector<int> tmp;
for(int i=2;i<n;i++)tmp.push_back(i);
auto res=Get(1,tmp,n);
printf("%d ",1);
for(auto it:res)printf("%d ",it);
printf("%d\n",n);
}
int main(){
scanf("%d",&T);
while(T--)solve();
return 0;
}
CF1208H Red Blue Tree
CF566C Logistical Questions
假设现在重心为 \(u\),考虑向 \(v\) 移动会不会更优。
设现在重心在 \((u,v)\) 这条边上,离 \(u\) 的距离为 \(x\),那么权值为:
求导之后可得:
只需判断一下此时是否有 \(v\) 满足导数大于 \(0\) 即可,若有则往 \(v\) 移动,我们每次可以以点分治的重心作为 \(u\) 来进行上面的过程,这样最多只会移动 \(O(\log)\) 次,总时间复杂度为\(O(n\log n)\) 。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n;
int ver[400005],ne[400005],head[400005],cnt,val[400005];
inline void link(int x,int y,int v){
ver[++cnt]=y;
ne[cnt]=head[x];
head[x]=cnt;val[cnt]=v;
}
long long dis[200005];
int siz[200005],rt,mxp[200005];
bool vis[200005];
void findrt(int x,int fi,int tot){
siz[x]=1;mxp[x]=0;
for(int i=head[x];i;i=ne[i]){
int u=ver[i];
if(vis[u]||u==fi)continue;
findrt(u,x,tot);siz[x]+=siz[u];
mxp[x]=max(mxp[x],siz[u]);
}
mxp[x]=max(mxp[x],tot-siz[x]);
if(mxp[rt]>mxp[x])rt=x;
}
double tmp,f[200005];
void calc(int x,int fi,int top,double dep){
tmp+=dis[x]*dep*sqrt(dep);
f[top]+=dis[x]*sqrt(dep);
for(int i=head[x];i;i=ne[i]){
int u=ver[i];
if(u==fi)continue;
calc(u,x,top,dep+val[i]);
}
}
double ANS;
int ans;
void solve(int x){
vis[x]=1;tmp=0;
double sum=0;
for(int i=head[x];i;i=ne[i]){
int u=ver[i];
f[u]=0;calc(u,x,u,val[i]);
sum+=f[u];
}
if(!ans||tmp<ANS)ANS=tmp,ans=x;
for(int i=head[x];i;i=ne[i]){
int u=ver[i];
if(vis[u])continue;
if(2*f[u]>sum){
mxp[rt=0]=n;findrt(u,u,siz[u]);
solve(rt);break;
}
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%lld",&dis[i]);
for(int i=1;i<n;i++){
int x,y,v;scanf("%d%d%d",&x,&y,&v);
link(x,y,v);link(y,x,v);
}
mxp[rt=0]=n;findrt(1,1,n);
solve(rt);
printf("%d %.10lf\n",ans,ANS);
return 0;
}
CF1083C Max Mex
我们要找的是一个最小的 \(x\) 使得包含编号为 \([1,x]\) 的点在树上的最小连通块不是一条链。
对于一个区间 \([l,r]\) ,区间内的点在树上的形态是可以维护并快速合并的,线段树维护每个区间内的点是否在同一条链上,以及链的两个端点,线段树上二分即可,时间复杂度 \(O(n\log^2 n)\) 或 \(O(n\log n)\) 。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,q;
int ver[400005],ne[400005],head[200005],tot;
inline void link(int x,int y){
ver[++tot]=y;
ne[tot]=head[x];
head[x]=tot;
}
int siz[200005],son[200005],fa[200005],dep[200005];
void dfs1(int x,int fi){
siz[x]=1;fa[x]=fi;dep[x]=dep[fi]+1;
for(int i=head[x];i;i=ne[i]){
int u=ver[i];
if(u==fi)continue;
dfs1(u,x);siz[x]+=siz[u];
if(siz[u]>siz[son[x]])son[x]=u;
}
}
int top[200005],dfn[200005],cnt;
void dfs2(int x,int fi){
top[x]=fi;dfn[x]=++cnt;
if(son[x])dfs2(son[x],fi);
for(int i=head[x];i;i=ne[i]){
int u=ver[i];
if(u==fa[x]||u==son[x])continue;
dfs2(u,u);
}
}
inline int lca(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]>dep[top[y]])x=fa[top[x]];
else y=fa[top[y]];
}return dep[x]>dep[y]?y:x;
}
inline int dist(int x,int y){
int lc=lca(x,y);
return dep[x]+dep[y]-2*dep[lc];
}
inline bool in(int x,int y,int z){
return dist(x,z)+dist(z,y)==dist(x,y);
}
struct node{
int l,r;bool flag;
node(int _l=0,int _r=0,bool _flag=1){
l=_l;r=_r;flag=_flag;
}
inline node operator +(const node &b)const{
if(flag||b.flag)return node(0,0,1);
{
node res(l,r,0);
if(in(res.l,res.r,b.l)&&in(res.l,res.r,b.r))return res;
}
{
node res(l,b.r,0);
if(in(res.l,res.r,b.l)&&in(res.l,res.r,r))return res;
}
{
node res(b.l,r,0);
if(in(res.l,res.r,l)&&in(res.l,res.r,b.r))return res;
}
{
node res(b.l,b.r,0);
if(in(res.l,res.r,l)&&in(res.l,res.r,r))return res;
}
{
node res(l,b.l,0);
if(in(res.l,res.r,r)&&in(res.l,res.r,b.r))return res;
}
{
node res(r,b.r,0);
if(in(res.l,res.r,l)&&in(res.l,res.r,b.l))return res;
}
return node(0,0,1);
}
}tree[800005];
void update(int loc,int v,int l=0,int r=n,int i=1){
if(loc<l||loc>r)return ;
if(l==r){
tree[i]=node(v,v,0);
return ;
}
int mid=(l+r)>>1;
update(loc,v,l,mid,i<<1);update(loc,v,mid+1,r,i<<1|1);
tree[i]=tree[i<<1]+tree[i<<1|1];
}
void query(node v=node(0,0,1),int l=0,int r=n,int i=1){
if(l==r){
printf("%d\n",l);
return ;
}
int mid=(l+r)>>1;node tmp=tree[i<<1];
if(l)tmp=v+tmp;
if(tmp.flag)query(v,l,mid,i<<1);
else query(tmp,mid+1,r,i<<1|1);
}
int a[200005];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=2;i<=n;i++){
int x;scanf("%d",&x);
link(x,i);link(i,x);
}
dfs1(1,1);
dfs2(1,1);
for(int i=1;i<=n;i++)update(a[i],i);
scanf("%d",&q);
while(q--){
int op;scanf("%d",&op);
if(op==1){
int x,y;scanf("%d%d",&x,&y);
swap(a[x],a[y]);update(a[x],x);update(a[y],y);
}
else query();
}
return 0;
}
数据结构题一
考虑 \(k=2\) 时的情况,答案就是直径,我们将直径的一个端点作为根,答案相当于选 \(k-1\) 条自上而下的链,第一个选的一定是直径,将直径上的权值设为 \(0\) ,然后再选择一条最长链,以此类推。
容易发现这样一来求出的就是长链剖分后前 \(k-1\) 长的长链,考虑怎样维护这些链,当我们给一条点加上一个权值时,要向上更新,看看将链顶父亲的重儿子改为链顶是否会更优,这个操作可以用 \(\text{LCT}\) 维护,当更新到直径时,如果当前的根不在直径上,还需要将根换为直径的另一个端点。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
namespace Treap{
int rt;
int le[100005],ri[100005],rk[100005],siz[100005],cnt;
long long val[100005],tot[100005];
queue<int> q;
inline int New(long long v){
if(!q.empty()){
int x=q.front();q.pop();le[x]=ri[x]=0;
val[x]=tot[x]=v;siz[x]=1;rk[x]=rand();
return x;
}++cnt;
val[cnt]=tot[cnt]=v;siz[cnt]=1;rk[cnt]=rand();
return cnt;
}
inline void pushup(int x){
siz[x]=siz[le[x]]+1+siz[ri[x]];
tot[x]=tot[le[x]]+val[x]+tot[ri[x]];
}
int merge(int x,int y){
if(!x||!y)return x|y;
if(rk[x]>rk[y]){
ri[x]=merge(ri[x],y);
pushup(x);return x;
}
le[y]=merge(x,le[y]);pushup(y);
return y;
}
void split(int x,long long y,int &a,int &b){
if(!x){a=b=0;return ;}
if(val[x]>=y){
split(ri[x],y,ri[x],b);
pushup(x);a=x;return ;
}
split(le[x],y,a,le[x]);pushup(x);b=x;
}
inline void insert(long long v){
int a=0,b=0,c=0;split(rt,v+1,a,b);split(b,v,b,c);b=merge(b,New(v));
rt=merge(a,merge(b,c));
}
inline void del(long long v){
int a=0,b=0,c=0;split(rt,v+1,a,b);split(b,v,b,c);
if(b)q.push(b);b=merge(le[b],ri[b]);rt=merge(a,merge(b,c));
}
long long Getk(int x,int k){
if(siz[x]<=k)return tot[x];
if(siz[le[x]]>=k)return Getk(le[x],k);
if(siz[le[x]]+1==k)return tot[le[x]]+val[x];
return tot[le[x]]+val[x]+Getk(ri[x],k-siz[le[x]]-1);
}
void print(int x=rt){
if(!x)return ;
print(le[x]);
printf("%lld ",val[x]);
print(ri[x]);
}
}
namespace LCT{
int son[2][100005],fa[100005];
inline bool isroot(int x){
return son[0][fa[x]]!=x&&son[1][fa[x]]!=x;
}
long long val[100005],siz[100005];
inline void pushup(int x){
siz[x]=siz[son[0][x]]+val[x]+siz[son[1][x]];
}
inline void rotate(int x){
int y=fa[x],z=fa[y];
if(!isroot(y))son[son[1][z]==y][z]=x;
bool is=(son[1][y]==x);
son[is][y]=son[!is][x];fa[son[is][y]]=y;
son[!is][x]=y;fa[y]=x;fa[x]=z;pushup(y);pushup(x);
}
bool rev[100005];
inline void push(int x){
if(rev[x]){
swap(son[0][x],son[1][x]);
rev[son[0][x]]^=1;rev[son[1][x]]^=1;
}rev[x]=0;
}
int stk[100005],top;
inline void splay(int x){
stk[++top]=x;
for(int i=x;!isroot(i);i=fa[i])stk[++top]=fa[i];
while(top)push(stk[top--]);
while(!isroot(x)){
int y=fa[x],z=fa[y];
if(!isroot(y)){
if((son[1][y]==x)^(son[1][z]==y))rotate(x);
rotate(y);
}rotate(x);
}
}
inline int Top(int x){
while(!isroot(x))x=fa[x];
return x;
}
inline void access(int x,long long v){
splay(x);Treap::del(siz[x]);val[x]+=v;siz[x]+=v;
int i=x;x=fa[x];
while(x){
splay(x);int z=son[1][x];
if(!x)break;
if(!fa[Top(x)]){
int T=Top(x);
if(2*siz[z]>siz[T]-val[x]){
while(son[1][z])z=son[1][z];//cout<<"makeroot "<<z<<endl;
splay(z);rev[z]^=1;splay(x);z=son[1][x];
}
}
if(siz[z]>=siz[i])break;
Treap::del(siz[x]);Treap::insert(siz[z]);son[1][x]=i;pushup(x);
i=x;x=fa[x];
}
Treap::insert(siz[i]);
}
}
int n,q;
int ver[200005],ne[200005],head[100005],cnt;
inline void link(int x,int y){
ver[++cnt]=y;
ne[cnt]=head[x];
head[x]=cnt;
}
void dfs(int x,int fi){
LCT::fa[x]=fi;
for(int i=head[x];i;i=ne[i]){
int u=ver[i];
if(u==fi)continue;
dfs(u,x);
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<n;i++){
int x,y;scanf("%d%d",&x,&y);
link(x,y);link(y,x);
}
dfs(1,0);
for(int i=1;i<=n;i++){
int w;scanf("%d",&w);
LCT::access(i,w);
}
// Treap::print();
// puts("\n---");
scanf("%d",&q);
while(q--){
int op;scanf("%d",&op);
if(op==0){
int x,y;scanf("%d%d",&x,&y);
LCT::access(x,y);
}
else {
int k;scanf("%d",&k);
printf("%lld\n",Treap::Getk(Treap::rt,k-1));
}
// Treap::print();
// puts("\n---");
}
return 0;
}
数据结构题二
「JOISC 2019 Day3」穿越时空 Bitaro
首先我们可以消除路程的影响,对于每个 \(i\) ,令 \(L[i]\leftarrow L[i]-i\), \(R[i]\leftarrow R[i]-i-1\)。
问题变为连续穿过几个区间,求整个路程中向下的最小距离,显然有贪心解法,且一个区间信息的具有结合律,考虑线段树维护。
一共有两种区间,当区间内的区间交集不为空时,整个区间等价于这个区间内所有区间的交集,否则最优方案一定是从某个高度走到另一个高度,可以通过分类讨论的方式维护。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,q;
int L[300005],R[300005];
struct node{
bool op;
int l,r;long long v;
node(bool _op=0,int _l=-1e9,int _r=1e9,long long _v=0){
op=_op;l=_l;r=_r;v=_v;
}
inline node operator +(node b){
if(op){
if(b.op)return node(1,l,b.r,v+max(r-b.l,0)+b.v);
if(r>b.r)return node(1,l,b.r,v+r-b.r+b.v);
return node(1,l,max(r,b.l),v+b.v);
}
if(b.op){
if(b.l>=l&&b.l<=r)return b;
if(b.l<l)return node(1,l,b.r,v+l-b.l+b.v);
return node(1,r,b.r,v+b.v);
}
if(max(l,b.l)<=min(r,b.r))return node(0,max(l,b.l),min(r,b.r),v+b.v);
if(r<b.l)return node(1,r,b.l,v+b.v);
return node(1,l,b.r,v+l-b.r+b.v);
}
}tree[1200005],rtree[1200005];
void build(int l=1,int r=n-1,int i=1){
if(l==r){
tree[i]=node(0,L[l]-l,R[l]-l-1,0);
rtree[i]=node(0,L[l]-n+l-1,R[l]-n+l-2,0);
return ;
}
int mid=(l+r)>>1;
build(l,mid,i<<1);build(mid+1,r,i<<1|1);
tree[i]=tree[i<<1]+tree[i<<1|1];
rtree[i]=rtree[i<<1|1]+rtree[i<<1];
}
void update(int loc,int l=1,int r=n-1,int i=1){
if(loc<l||loc>r)return ;
if(l==r){
tree[i]=node(0,L[l]-l,R[l]-l-1,0);
rtree[i]=node(0,L[l]-n+l-1,R[l]-n+l-2,0);
return ;
}
int mid=(l+r)>>1;
update(loc,l,mid,i<<1);update(loc,mid+1,r,i<<1|1);
tree[i]=tree[i<<1]+tree[i<<1|1];
rtree[i]=rtree[i<<1|1]+rtree[i<<1];
}
node query(int fr,int to,int l=1,int r=n-1,int i=1){
if(fr>r||to<l)return node();
if(fr<=l&&to>=r)return tree[i];
int mid=(l+r)>>1;
return query(fr,to,l,mid,i<<1)+query(fr,to,mid+1,r,i<<1|1);
}
node rquery(int fr,int to,int l=1,int r=n-1,int i=1){
if(fr>r||to<l)return node();
if(fr<=l&&to>=r)return rtree[i];
int mid=(l+r)>>1;
return rquery(fr,to,mid+1,r,i<<1|1)+rquery(fr,to,l,mid,i<<1);
}
int main(){
scanf("%d%d",&n,&q);
for(int i=1;i<n;i++)scanf("%d%d",&L[i],&R[i]);
if(n>1)build();
while(q--){
int op;scanf("%d",&op);
if(op==1){
int p;scanf("%d",&p);
scanf("%d%d",&L[p],&R[p]);update(p);
}
else {
int a,b,c,d;
scanf("%d%d%d%d",&a,&b,&c,&d);
if(a<c){
node res=node(1,b-a,b-a,0)+query(a,c-1)+node(1,d-c,d-c,0);
printf("%lld\n",res.v);
}
else {
node res=node(1,b-n+a-2,b-n+a-2,0)+rquery(c,a-1)+node(1,d-n+c-2,d-n+c-2,0);
printf("%lld\n",res.v);
}
}
}
return 0;
}
CF GYM 102979 K. Knowledge Is...
我们将所有区间按左端点从小到大排序,那么不难发现当我们尝试匹配一个区间 \([l_i,r_i]\) 时,如果存在一个候选集合中的区间能与之匹配,我们肯定会贪心地选择能匹配的区间中右端点 \(<l_i\) 且最小的,这个显然可以 \(\text{set}\) 维护。
考虑如下 \(\text{hack}\):
4
1 2
3 5
4 7
6 8
原因在于你选择将 \([1,2]\) 与 \([3,5]\) 匹配后就将这个区间固定下来了,实际上 \([4,7]\) 也能匹配 \([1,2]\),但其不能与 \([6,8]\) 匹配,而 \([3,5]\) 匹配,也就是说实际上 \([4,7]\) 与 \([1,2]\) 匹配更加能够尽可能用完右端点大的区间,因此我们考虑加个反悔的元素:我们将所有已经匹配的区间对中较右的再扔进一个 \(\text{set}\),如果一个区间 \([l_i,r_i]\) 不能匹配候选集合中的区间,我们就考虑反悔集合中右端点最小的区间 \([L,R]\),如果 \(R<r_i\) 我们就用 \([l_i,r_i]\) 匹配 \([L,R]\) 匹配的区间然后将 \([L,R]\) 加入候选集合。
时间复杂度 \(O(n\log n)\) 。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m,k;
int X[300005],Y[300005],Ans[300005],Cnt;
struct Q1{
int x,y,id;
inline bool operator <(const Q1 &B)const{
return y^B.y?y<B.y:id<B.id;
};
}S[300005];
struct Q2{
int x,I1,I2;
inline bool operator <(const Q2 &B)const{
return x^B.x?x<B.x:I2<B.I2;
};
}Tp,Ns;
set<Q2> T1,T2,T3;
set<Q2>::iterator P1,P2;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d%d",&X[i],&Y[i]),S[i].x=X[i],S[i].y=Y[i],S[i].id=i;
sort(S+1,S+n+1);
for(int i=n;i;i--){
P1=T1.lower_bound((Q2){S[i].y+1,0,0});if(P1==T1.end()) {
P1=P2=T2.lower_bound((Q2){S[i].y+1,0,0});for(;P2!=T2.end();P2++) T3.insert((Q2){X[(*P2).I2],(*P2).I1,(*P2).I2});T2.erase(P1,T2.end());
if(T3.empty())T1.insert((Q2){S[i].x,S[i].y,S[i].id});
else{
P2=T3.end(),P2--;Tp=*P2;T3.erase(P2);Ns=(Q2){X[Tp.I1],Tp.I2,Tp.I1};T3.count(Ns)&&(T3.erase(Ns),0);Ns.x=X[Tp.I2];T2.count(Ns)&&(T2.erase(Ns),0);
Ans[S[i].id]=Ans[Tp.I1];
Ans[Tp.I2]=0;T1.insert((Q2){X[Tp.I2],Y[Tp.I2],Tp.I2});T2.insert((Q2){S[i].x,S[i].id,Tp.I1});T2.insert((Q2){X[Tp.I1],Tp.I1,S[i].id});
} continue;
}
Ans[S[i].id]=Ans[(*P1).I2]=++Cnt;T2.insert((Q2){S[i].x,S[i].id,(*P1).I2});T2.insert((Q2){X[(*P1).I2],(*P1).I2,S[i].id});T1.erase(P1);
}
for(int i=1;i<=n;i++){
if(!Ans[i])Ans[i]=++Cnt;
printf("%d ",Ans[i]>m?0:Ans[i]);
}
return 0;
}
CF GYM 102586 L. Yosupo’s Algorithm
首先考虑最暴力的做法:枚举可以匹配的点对,假设第 \(i\) 个点对的两个点的横坐标分别为 \(a_i,b_i\),那么我们将 \((a_i,b_i)\) 看作二维平面上的一个点,问题就转化为二维数点,离线 \(+\) 树状数组解决。
考虑优化,注意到我们将所有 \(O(n^2)\) 个点对都存下来有点浪费,因此考虑删除一些无用点对。我们考虑将所有点按 \(y\) 坐标排序后分治,分治到区间 \([l,r]\) 时,设 \(mid=\frac{l+r}{2}\),那么有这样一个性质:对于 \([l,mid]\) 中的红点 \(i\) 和 \([mid+1,r]\) 中的蓝点 \(j\),点对 \((i,j)\) 是有用的当且仅当 \(i\) 是 \([l,mid]\) 中红点权值最大的,或者 \(j\) 是 \([mid+1,r]\) 中蓝点权值最大的。
证明异常容易的,考虑反证法,假设对于 \([l,mid]\) 中的红点 \(i\) 和 \([mid+1,r]\) 中的蓝点 \(j\),点对 \((i,j)\) 是 \([L,R]\) 询问的最优答案,但 \(i\) 不是 \([l,mid]\) 中红点权值最大的,且 \(j\) 不是 \([mid+1,r]\) 中蓝点权值最大的,那么我们考虑 \(i,j\) 在不在 \([L,R]\) 中的状态,显然它们要么同时在 \([L,R]\) 中要么同时不在,考虑 \([l,mid]\) 中权值最大的红点 \(X\) 以及 \([mid+1,r]\) 中权值最大的蓝点 \(Y\),如果 \(X,Y\) 在不在 \([L,R]\) 中的状态至少一者与 \(i(j)\) 相同,那么我们把其中一个点换成这个点答案肯定最大,否则 \(X,Y\) 关于 \([L,R]\) 肯定相同,它们肯定会对答案产生更大的贡献。
这样点对数就降到了 \(n\log n\),时间复杂度 \(O(n\log^2n+q\log n)\) 。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
using pi=std::pair<int,int>;
char ibuf[1<<25|1],*iS=ibuf;
inline int read(){
int x=0,f=1;
while(isspace(*iS))++iS;if(*iS=='-')f=-1,++iS;
while(isdigit(*iS))(x*=10)+=*iS++&15;
return f*x;
}
int n,q,cx,cp,tx[1200005],ans[1200005],bit[1200005];
pair<int,int> qry[1200005],pr[4000005];
vector<int>vec[1200005];
struct node{
int x,y,w,col;
inline bool operator <(const node &b){
return y<b.y;
}
}a[200005];
inline void add(int p,int v){
for(;p<=cx;p+=p&-p)bit[p]=max(bit[p],v);
}
inline int ask(int p){
int r=-1e9;
for(;p;p^=p&-p)r=max(r,bit[p]);
return r;
}
void solve(int l,int r){
if(l==r) return;
int mid=(l+r)/2,pos;solve(l,mid),solve(mid+1,r);
pos=0;
for(int i=l;i<=mid;i++){
if(!a[i].col&&a[i].w>a[pos].w)pos=i;
}
if(pos)for(int i=mid+1;i<=r;i++){
if(a[i].col)pr[++cp]={pos,i};
}
pos=0;
for(int i=mid+1;i<=r;i++){
if(a[i].col&&a[i].w>a[pos].w)pos=i;
}
if(pos)for(int i=l;i<=mid;i++){
if(!a[i].col)pr[++cp]={i,pos};
}
}
int main(){
fread(ibuf,1,1<<25,stdin),n=read();
for(int i=1;i<=n;i++){
a[i]={tx[++cx]=read(),read(),read(),0};
}
for(int i=1;i<=n;++i){
a[i+n]={tx[++cx]=read(),read(),read(),1};
}
sort(a+1,a+n+n+1,[](const node&a,const node&b){return a.y<b.y;});
solve(1,n+n);
q=read();
memset(ans+1,-1,4*q);
for(int i=1;i<=q;i++){
qry[i]={read(),read()},tx[++cx]=qry[i].first;tx[++cx]=qry[i].second;
}
sort(tx+1,tx+cx+1),cx=std::unique(tx+1,tx+cx+1)-tx-1;
for(int i=1;i<=n+n;i++){
a[i].x=lower_bound(tx+1,tx+cx+1,a[i].x)-tx;
}
for(int i=1;i<=q;i++){
qry[i]={lower_bound(tx+1,tx+cx+1,qry[i].first)-tx,lower_bound(tx+1,tx+cx+1,qry[i].second)-tx},vec[qry[i].first].push_back(i);
}
sort(pr+1,pr+cp+1,[](const pi&i,const pi&j){return a[i.first].x<a[j.first].x;}),memset(bit+1,-0x3f,4*cx);
for(int i=1,j=1;i<=cx;i++){
for(;j<=cp&&a[pr[j].first].x<=i;++j) add(cx-a[pr[j].second].x+1,a[pr[j].first].w+a[pr[j].second].w);
for(int id:vec[i]) ans[id]=std::max(ans[id],ask(cx-qry[id].second+1));
}
reverse(pr+1,pr+cp+1);memset(bit+1,-0x3f,4*cx);
for(int i=cx,j=1;i;i--){
for(;j<=cp&&a[pr[j].first].x>=i;++j) add(a[pr[j].second].x,a[pr[j].first].w+a[pr[j].second].w);
for(int id:vec[i]) ans[id]=std::max(ans[id],ask(qry[id].second));
}
for(int i=1;i<=q;i++)printf("%d\n",ans[i]);
return 0;
}