MX 暑假集训 7.19

テトリス / 重音テトSV

何意味,打开题单发现有一半的题都做过。

一些板子就不讲了。

[FJOI2016] 神秘数

题意

给定一个长度为 \(n\) 的序列 \(a\),定义一个可重集的权值 \(f(S)\) 为最小的不能用 \(S\) 的子集和表示的正整数。

\(m\) 次询问,每次给定 \(l,r\),询问 \(a_l,a_{l+1},\dots ,a_r\) 构成可重集的权值。

\(1\le n,m\le 10^5,\sum{a_i}\le 10^9\)

solution

考虑类似于背包 dp,维护最大的 \(x\) 满足 \([1,x]\) 都可以被表示,那么若 \(\sum\limits_{a_i\le x}{a_i}<x+1\),说明 \(x+1\) 不能被表示,否则 \([1,\sum\limits_{a_i\le x}{a_i}]\) 都可以被表示,将 \(x\) 更新为 \(\sum\limits_{a_i\le x}{a_i}\) 即可。

容易发现每两次更新必然使 \(x\) 翻倍,所以总次数不超过 \(O(\log V)\)

利用主席树维护即可做到 \(O(n\log n\log V)\)

老师讲了一个倍增值域分块的做法,同样是维护 \(x\),对值域分块,每个块为 \([2^k,2^{k+1}-1]\),若 \(x\) 不小于 \(x\) 所在块的最小值,那么 \(x\) 加上这个块的最小值后一定会大于这个块的右端点,即覆盖整个块,所以每个块只需要维护最小值及和即可,运用一些科技可以做到时间 \(O(n\log V)\),空间 \(O(n)\)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
const int N=1e5+10;
const int V=1e9+10;

inline void read(int &x) {
    x=0;
    char ch=getchar();
    while (ch<48) ch=getchar();
    while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n,m,tot;
int a[N],rt[N];

int ls[N<<5],rs[N<<5],v[N<<5];

inline int Node(int x) {
	v[++tot]=v[x];
	ls[tot]=ls[x];
	rs[tot]=rs[x];
	return tot;
}

inline void update(int &x,int y,int l,int r,int k) {
	x=Node(y);v[x]+=k;
	if (l==r) return;
	int mid=l+r>>1;
	if (k<=mid) update(ls[x],ls[y],l,mid,k);
	else update(rs[x],rs[y],mid+1,r,k);
}

inline int query(int p,int q,int l,int r,int x,int y) {
	if (x<=l&&y>=r) return v[q]-v[p];
	int mid=l+r>>1,res=0;
	if (x<=mid) res+=query(ls[p],ls[q],l,mid,x,y);
	if (y>mid) res+=query(rs[p],rs[q],mid+1,r,x,y);
	return res;
}

int main() {
	read(n);
	f(i,1,n) read(a[i]);
	f(i,1,n) update(rt[i],rt[i-1],1,V,a[i]);
	read(m);
	while (m--) {
		int x,y,ans=1;
		read(x);read(y);
		while (1) {
			int res=query(rt[x-1],rt[y],1,V,1,ans);
			if (res>=ans) ans=res+1;
			else break;
		}
		printf("%d\n",ans);
	}
    return 0;
}

[JSOI2018] 列队

solution

\([l,r]\) 这段区间的人放在值域上,显然相对位置不变,写成式子就是一个带绝对值的式子。

那么就是一个前缀的人向右边跑,其余人向左边跑,二分找到这个分界点,拆开绝对值就可以直接计算。

二分时可以钦定一个人在 \(mid+1\) 的位置,看他向左跑还是向右跑,从而找到对应的子区间递归下去。

用可持久化线段树维护即可做到 \(O(n\log n)\)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=5e5+10;
constexpr int V=1e6;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n,m,tot;
int rt[N];

struct Segment_Tree {
	struct Node{int ls,rs,siz;ll sum;}tr[N<<6];
	#define ls(x) tr[x].ls
	#define rs(x) tr[x].rs
	
	inline int newNode(int x) {
		tr[++tot]=tr[x];
		return tot;
	}
	
	inline void update(int x,int l,int r,int &now) {
		now=newNode(now);
		tr[now].siz++;
		tr[now].sum+=x;
		if (l==r) return ;
		int mid=l+r>>1;
		if (x<=mid) update(x,l,mid,ls(now));
		else update(x,mid+1,r,rs(now));
	}
	
	inline int query_mid(int x,int y,int l,int r,int sum,int k) {
		if (l>k-1+sum) return 0;
		if (l==r) return l;
		int mid=l+r>>1,sz=tr[ls(y)].siz-tr[ls(x)].siz;
		if (mid+1<=k-1+sum+sz) return query_mid(rs(x),rs(y),mid+1,r,sum+sz,k);
		return query_mid(ls(x),ls(y),l,mid,sum,k);
	}
	
	inline ll query_sum(int x,int y,int l,int r,int p,int q) {
		if (x<=l&&y>=r) return tr[q].sum-tr[p].sum;
		int mid=l+r>>1;ll res=0;
		if (x<=mid) res=query_sum(x,y,l,mid,ls(p),ls(q));
		if (y>mid) res+=query_sum(x,y,mid+1,r,rs(p),rs(q));
		return res;
	}
	
	inline ll query_siz(int x,int y,int l,int r,int p,int q) {
		if (x<=l&&y>=r) return tr[q].siz-tr[p].siz;
		int mid=l+r>>1,res=0;
		if (x<=mid) res=query_siz(x,y,l,mid,ls(p),ls(q));
		if (y>mid) res+=query_siz(x,y,mid+1,r,rs(p),rs(q));
		return res;
	}
}tr;

int main() {
	read(n);read(m);
	f(i,1,n) {
		int x;read(x);rt[i]=rt[i-1];
		tr.update(x,1,V,rt[i]);
	}
	while (m--) {
		int l,r,k;read(l);read(r);read(k);
		int mid=tr.query_mid(rt[l-1],rt[r],1,V,0,k);
		int siz=(mid?tr.query_siz(1,mid,1,V,rt[l-1],rt[r]):0);
		ll v1=1ll*(k+k+siz-1)*siz/2-(mid?tr.query_sum(1,mid,1,V,rt[l-1],rt[r]):0);
		ll v2=(mid!=V?tr.query_sum(mid+1,V,1,V,rt[l-1],rt[r]):0)-1ll*(k+siz+k+r-l)*(r-l+1-siz)/2;
		printf("%lld\n",v1+v2);
	}
	return 0;
}

[TJOI2018] 异或

题意

给定一棵 \(n\) 个节点的有根树,点有点权,\(m\) 次询问,每次询问形式如下:

  • 给定 \(x,k\),求 \(x\) 子树内一个点的点权异或上 \(k\) 的最大值;
  • 给定 \(x,y,k\),求 \(x\)\(y\) 路径上一个点的点权异或上 \(k\) 的最大值。

\(1\le n,m\le 10^5\)

solution

显然可以直接树剖变成序列问题,然后就是可持久化字典树板子,时间复杂度 \(O(n\log ^2 n)\)

但是还有更优秀的做法,由于可差分,对子树拍到 dfn 序上,和序列没有区别,链的话就类似于 Count on a tree 那样做个可持久化字典树,根据差分查询某一位的信息,时间复杂度 \(O(n\log n)\)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define rg register
#define f(i,l,r) for (rg int i=l;i<=r;i++)
#define F(i,l,r) for (rg int i=l;i>=r;i--)
const int N=1e5+10;
const int inf=1e9+10;

inline void read(int &x) {
    x=0;
    char ch=getchar();
    while (ch<48) ch=getchar();
    while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int tr[N*60][2],last[N*60];
int cnt,tim,num[31];

inline void calc(int x) {
	f(i,1,30) {
		if (x&(1<<(i-1))) num[i]=1;
		else num[i]=0;
	}
}

inline int newnode(int x) {
	tr[++cnt][0]=tr[x][0];
	tr[cnt][1]=tr[x][1];
	last[cnt]=tim;
	return cnt;
}

inline void ins(int now) {
	int len=30;
	while (len) {
		int pos=newnode(tr[now][num[len]]);
		tr[now][num[len]]=pos;
		now=tr[now][num[len]];
		len--;
	}
}

inline int query(int lim,int now) {
	int len=30,res=0;
	while (len) {
		if (tr[now][num[len]^1]&&last[tr[now][num[len]^1]]>=lim) {
			res+=(1<<(len-1));
			now=tr[now][num[len]^1];
		}
		else now=tr[now][num[len]];
		if (!now) break;
		len--;
	}
	return res;
}

int head[N],tot1,tot2;
struct edge{int next,to;}e[N<<1];
inline void add(int u,int v) {
	e[++tot1]={head[u],v};
	head[u]=tot1;
}

int fa[N],siz[N],dep[N],son[N];
int dfn[N],top[N],rnk[N];

inline void dfs1(int now) {
	siz[now]=1;
	son[now]=-1;
	for (int i=head[now];i;i=e[i].next) {
		int v=e[i].to;
		if (dep[v]) continue;
		fa[v]=now;
		dep[v]=dep[now]+1;
		dfs1(v);
		siz[now]+=siz[v];
		if (son[now]==-1||siz[v]>siz[son[now]]) son[now]=v;
	}
}

inline void dfs2(int now,int t) {
	top[now]=t;
	dfn[now]=++tot2;
	rnk[tot2]=now;
	if (son[now]==-1) return;
	dfs2(son[now],t);
	for (int i=head[now];i;i=e[i].next) {
		int v=e[i].to;
		if (v==fa[now]||v==son[now]) continue;
		dfs2(v,v);
	}
}

int n,q;
int c[N],rt[N<<1];

inline void build() {
	rt[0]=++cnt;
	ins(1);
	f(i,1,n) {
		tim=i;
		rt[i]=newnode(rt[i-1]);
		calc(c[rnk[i]]);
		ins(rt[i]);
	}
}

inline void get(int x,int y) {
	int ans=0;
	while (top[x]!=top[y]) {
		if (dep[top[x]]<dep[top[y]]) swap(x,y);
		ans=max(ans,query(dfn[top[x]],rt[dfn[x]]));
		x=fa[top[x]];
	}
	if (dep[x]>dep[y]) swap(x,y);
	ans=max(ans,query(dfn[x],rt[dfn[y]]));
	printf("%d\n",ans);
}

int main() {
	read(n);read(q);
	f(i,1,n) read(c[i]);
	f(i,1,n-1) {
		int u,v;
		read(u);read(v);
		add(u,v);add(v,u);
	} 
	dep[1]=1;
	dfs1(1);
	dfs2(1,1);
    build();
	while (q--) {
		int op,x,y,z;
		read(op);read(x);read(y);
		if (op==1) {
			calc(y);
			printf("%d\n",query(dfn[x],rt[dfn[x]+siz[x]-1]));
		}
		else {
			read(z);
			calc(z);
			get(x,y);
		}
	}
    return 0;
}

[SDOI2013] 森林

题意

你需要维护一个森林,共有 \(n\) 个节点,点有点权,有 \(q\) 次操作,每次操作形式如下。

  • 给定 \(x,y\),在 \(x,y\) 之间增加一条边,保证增加后仍是森林;
  • 给定 \(x,y,k\),询问 \(x,y\) 这条链上的 \(k\) 小点权。

\(1\le n,q\le 8\times 10^4\)

solution

直接用可持久化线段树做启发式合并可以做到修改总时间复杂度 \(O(n\log^2 n)\),查询单次 \(O(\log n)\)

我们可以在启发式合并的时候积累一些树先不做合并,以后再同时合并,通过一些倍增值域分块讨论的手法可以做到 \(O(n\log^{1.5}n)\)

Code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=8e4+10;
constexpr int P=(N<<6);
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

vector<int> gep;
struct PT_Node {
	int rbh[P],tot,top;
	inline int get() {int ret=(top?rbh[top--]:++tot);gep.push_back(ret);return ret;}
	inline void clear(int x) {rbh[++top]=x;}
}pts;

struct Segment_Tree {
	struct Node {int ls,rs,siz;}tr[P];
	#define ls(x) tr[x].ls
	#define rs(x) tr[x].rs
	
	inline int newNode(int x) {
		int ret=pts.get();
		return tr[ret]=tr[x],ret;
	}
	
	inline void update(int x,int l,int r,int &now) {
		now=newNode(now);
		tr[now].siz++;
		if (l==r) return;
		int mid=l+r>>1;
		if (x<=mid) update(x,l,mid,ls(now));
		else update(x,mid+1,r,rs(now));
	}
	
	inline int query(int p1,int p2,int p3,int p4,int k,int l,int r) {
		if (l==r) return l;
		int mid=l+r>>1;
		int sz=tr[ls(p1)].siz+tr[ls(p2)].siz-tr[ls(p3)].siz-tr[ls(p4)].siz;
		if (sz>=k) return query(ls(p1),ls(p2),ls(p3),ls(p4),k,l,mid);
		return query(rs(p1),rs(p2),rs(p3),rs(p4),k-sz,mid+1,r);
	}
}tr;

int n,m,q,L;
int a[N],mp[N],num[N<<2],rt[N];
int f[N],siz[N],dep[N],fa[N][20];
vector<int> e[N],pt[N];

inline int find(int x) {
	if (f[x]==x) return x;
	return f[x]=find(f[x]);
}

inline void dfs(int now,int fat) {
	fa[now][0]=fat;rt[now]=rt[fat];dep[now]=dep[fat]+1;
	f(i,1,17) fa[now][i]=fa[fa[now][i-1]][i-1];
	tr.update(a[now],1,L,rt[now]);
	for (int v:e[now]) if (v^fat) dfs(v,now);
}

inline void update(int x,int y) {
	if (siz[find(x)]>siz[find(y)]) swap(x,y);
	for (int k:pt[find(x)]) pts.clear(k);
	pt[find(x)].clear();
	e[x].push_back(y);
	e[y].push_back(x);
	dfs(x,y);siz[find(y)]+=siz[find(x)];f[find(x)]=find(y);
	for (int k:gep) pt[find(y)].push_back(k);
	gep.clear();
}

inline int lca(int x,int y) {
	if (dep[x]<dep[y]) swap(x,y);
	int k=dep[x]-dep[y];
	while (k) {
		x=fa[x][num[k&-k]];
		k^=(k&-k);
	}
	if (!(x^y)) return x;
	F(i,17,0) if (fa[x][i]^fa[y][i]) x=fa[x][i],y=fa[y][i];
	return fa[x][0];
}

inline int query(int x,int y,int k) {
	int k1=lca(x,y),k2=fa[k1][0];
	return mp[tr.query(rt[x],rt[y],rt[k1],rt[k2],k,1,L)];
}

int main() {
	read(n);read(n);read(m);read(q);
	f(i,1,n) read(a[i]),mp[++L]=a[i],f[i]=i,siz[i]=1;
	sort(mp+1,mp+1+L);
	L=unique(mp+1,mp+1+L)-mp-1;
	f(i,0,17) num[1<<i]=i;
	f(i,1,n) {
		a[i]=lower_bound(mp+1,mp+1+L,a[i])-mp;
		tr.update(a[i],1,L,rt[i]);
		for (int k:gep) pt[i].push_back(k);
		gep.clear();
	}
	f(i,1,m) {
		int x,y;read(x);read(y);
		update(x,y);
	}
	int last_ans=0;char op[3];
	while (q--) {
		int x,y,k;
		scanf("%s",op+1);read(x);read(y);
		if (op[1]=='Q') read(k),printf("%d\n",last_ans=query(last_ans^x,last_ans^y,last_ans^k));
		else update(last_ans^x,last_ans^y);
	}
	return 0;
}

[十二省联考 2019] 异或粽子

题意

给定一个长度为 \(n\) 的序列,求不同子区间异或和的 \(k\) 大值。

\(1\le n\le 5\times 10^5,1\le k\le 2\times 10^5\)

solution

首先转成前缀异或和,每个区间对应两个前缀异或和的异或,我们考虑每个位置对于前面一个位置异或最大值,每个位置塞进一个优先队列里,每次取出最大值,并对这个位置删掉对应的最大值,用可持久化字典树即可做到时间 \(O((n+k)\log n)\),空间 \(O((n+k)\log n)\)

其实可以做到空间 \(O(n\log n)\)

考虑将每个位置前缀异或都存入一个字典树,每个位置共用全局字典树,同样取出每个位置对其他位置异或最大值,一个区间对应的异或和会被计算两次,所以 \(k\leftarrow k\times 2\),然后我们对一个位置删除一个数,若这个数对应这个位置的第 \(k\) 大(初始时为第 \(1\) 大),那么删除掉这个位置对应的异或位置后的最大值其实就是异或第 \(k\) 大,直接在字典树上查询即可,这样不需要可持久化。

Code
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
#define uint unsigned int
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=5e5+10;
constexpr int P=(N<<6);
constexpr int inf=1e9+10;

inline void readu(uint &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n,m;
uint a[N];
int tmp[N];

struct Node {
	int id;uint val;
	inline bool operator <(const Node &x)const {return val<x.val;}
};
priority_queue<Node> q;

namespace Trie {
	constexpr int D=31;
	int ch[P][2],siz[P],cnt=1;
	
	inline void insert(uint v) {
		int now=1;
		F(len,D,0) {
			bool d=((v>>len)&1);
			siz[now]++;
			if (!ch[now][d]) ch[now][d]=++cnt;
			now=ch[now][d];
		}
		siz[now]++;
	}
	
	inline uint query_kth(uint v,int k) {
		uint ret=0;int now=1;
		F(len,D,0) {
			bool d=((v>>len)&1);
			if (siz[ch[now][d^1]]>=k) now=ch[now][d^1],ret|=(1u<<len);
			else k-=siz[ch[now][d^1]],now=ch[now][d];
		}
		return ret;
	}
};

inline uint solve() {
	Node tp=q.top();
	q.pop();
	int now=tp.id;
	q.push({now,Trie::query_kth(a[now],++tmp[now])});
	return tp.val;
}

int main() {
	scanf("%d%d",&n,&m);
	f(i,0,n) tmp[i]=1;
	uint sum=0;m*=2;
	Trie::insert(sum);
	f(i,1,n) {
		uint x;readu(x);sum^=x;
		Trie::insert(a[i]=sum);
	}
	f(i,0,n) q.push({i,Trie::query_kth(a[i],1)});
	ll ans=0;
	f(i,1,m) ans+=solve();
	printf("%lld",ans/2);
	return 0;
}

T-Shirts

题意

\(n\) 件 T 恤,每件 T 恤都有对应的质量和价格,\(m\) 个人要买 T 恤,每个人都有一个初始的预算,且每个人的策略都是购买他买得起的质量最大的 T 恤,直到所有 T 恤都被买过或者他买不起任何一件没买过的 T 恤,对于同一个顾客,每件 T 恤最多被购买一次,求每个人总共需要花多少钱。

\(1\le n,m\le 2\times 10^5\)

solution

考虑将顾客离线下来,按照质量降序排序,从前往后考虑每个 T 恤会被哪些顾客购买。

若当前 T 恤价格为 \(c\),那么剩余钱数不小于 \(c\) 的人就会购买,那么用一棵平衡树维护顾客的剩余预算,\([0,c)\) 则不变,\([c,+\infty )\) 则减去 \(c\),我们发现对于 \([c,2c]\) 内的顾客,买完后钱数变为 \([0,c]\),与不购买的顾客钱数的值域有交,所以我们要将剩余预算 \([c,2c]\) 内的顾客拿出来减去后再暴力地重新插入平衡树,钱数 \((2c,+\infty )\) 的顾客直接打懒标记即可。

由于每次暴力重新加入一个顾客会使这个顾客钱数至少减半,所以复杂度为 \(O(n\log n\log V)\)

有个倍增值域分块可以做到在线。

将 T 恤按质量降序排序,然后考虑价格,值域分块,每个块为 \([2^k,2^{k+1}-1]\),对于每个块,不考虑价格大于这个块值域上界的 T 恤,若一件 T 恤价格在这个块值域区间内,则视为“大件”,否则视为“小件”。

对于当前预算 \(c\),若经过购买从一个块降到了另一个块,则称作“降级”,显然若 \(c\) 碰到了一个块内的“大件”则必然降级。

对于每个块,预处理出每个位置最先碰到哪个“大件”,因为“小件”使 \(c\) 的减少过程是固定的,所以求出每个位置对应最先碰到哪个“大件”并不难。

若无法碰到这个块的“大件”,则二分确定在哪里 \(c\) 会降级,时间复杂度 \(O(n\log n\log V)\),没写过不懂难不难写。

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define int long long
#define ll long long
#define ls(x) ch[x][0]
#define rs(x) ch[x][1] 
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
const int N=2e5+10;
const ll inf=1e18+10;

inline void read(int &x) {
    x=0;
    char ch=getchar();
    while (ch<48) ch=getchar(); 
    while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n,m;

struct Node{int c,q;}a[N];
inline bool cmp1(Node a,Node b) {return (a.q==b.q)?(a.c<b.c):(a.q>b.q);}

struct Que{int v,id;}b[N];
inline bool cmp2(Que a,Que b) {return a.v<b.v;}

int rt,tot;
int ch[N][2],f[N],siz[N];
int ans[N],id[N];
int v[N],tag[N],tim[N];
int rbh[N],cnt;
int st[N],top;

inline bool isrson(int x) {return rs(f[x])==x;}
inline void push_up(int x) {siz[x]=siz[ls(x)]+siz[rs(x)]+1;}
inline void clear(int x) {siz[x]=ls(x)=rs(x)=f[x]=id[x]=v[x]=tag[x]=tim[x]=0;}

inline void push_down(int x) {
	v[ls(x)]+=tag[x];
	v[rs(x)]+=tag[x];
	ans[id[ls(x)]]+=tim[x];
	ans[id[rs(x)]]+=tim[x];
	tim[ls(x)]+=tim[x];
	tim[rs(x)]+=tim[x];
	tag[ls(x)]+=tag[x];
	tag[rs(x)]+=tag[x];
	tim[x]=0;
	tag[x]=0;
}

inline void push_all(int now) {
	if (!now) return;
	push_down(now);
	push_all(ls(now));
	push_all(rs(now));
}

inline int newnode(int k,int i) {
	int now=cnt?rbh[cnt--]:++tot;
	clear(now);
	v[now]=k;
	id[now]=i;
	siz[now]=1;
	return now;
}

inline int build(int l,int r,int fa) {
	if (l>r) return 0;
	int mid=l+r>>1,now=newnode(b[mid].v,b[mid].id);
	f[now]=fa;
	ls(now)=build(l,mid-1,now);
	rs(now)=build(mid+1,r,now);
	push_up(now);
	return now;
}

inline void rotate(int x) {
	int y=f[x],d=isrson(x);
	if (f[y]) ch[f[y]][isrson(y)]=x;
	f[x]=f[y];
	ch[y][d]=ch[x][d^1];
	if (ch[y][d]) f[ch[y][d]]=y;
	ch[x][d^1]=y;
	f[y]=x;
	push_up(y);
	push_up(x);
}

inline void splay(int x,int k) {
	for (int y;(y=f[x])!=k;rotate(x))
		if (f[y]!=k) rotate(isrson(y)==isrson(x)?y:x);
	if (!k) rt=x;
}

inline int get_rank(int now,int k,int res) {
	if (!now) return res;
	push_down(now);
	if (k<=v[now]) return get_rank(ls(now),k,res);
	return get_rank(rs(now),k,res+siz[ls(now)]+1);
}

inline int find(int now,int k) {
	push_down(now);
	if (k<=siz[ls(now)]) return find(ls(now),k);
	if (k==siz[ls(now)]+1) return now;
	return find(rs(now),k-siz[ls(now)]-1);
}

inline void debug(int now) {
	if (!now) return;
	debug(ls(now));
//	printf("%lld ",v[now]);
	debug(rs(now));
}

inline void ins(Que q) {
//	debug(rt);
//	puts("");
	int pos=get_rank(rt,q.v,0);
	int x=find(rt,pos),y=find(rt,pos+1);
//	printf("find::%d %d %d\n",pos,v[x],v[y]);
	splay(x,0);splay(y,x);
	int now=newnode(q.v,q.id);
	f[now]=y;ls(y)=now;
	push_up(y);
	push_up(x);
	splay(now,0);
}

inline int pre(int now) {
	splay(now,0);
	int x=ls(now);
	while (rs(x)) x=rs(x);
	return x;
}

inline int nxt(int now) {
	splay(now,0);
	int x=rs(now);
	while (ls(x)) x=ls(x);
	return x;
}

inline void del(int k) {
	rbh[++cnt]=k;
	int x=pre(k),y=nxt(k);
	splay(x,0);splay(y,x);
	ls(y)=0;
	push_up(y);
	push_up(x);
}

inline void dfs(int now,int c) {
	if (!now) return;
//	printf("dfs:%lld\n",v[now]);
	push_down(now);
	v[now]-=c;
	ans[id[now]]++;
	if (v[now]<c) st[++top]=now;
	dfs(ls(now),c);
	dfs(rs(now),c);
}

inline void solve(int c) {
//	printf("solve:%d \n",c);
//	debug(rt);
//	puts("");
//	f(i,1,tot) printf("tr:%d %d %d\n",i,v[i],siz[i]);
	int k1=get_rank(rt,c,0),k2=get_rank(rt,c*2,0);
	int x=find(rt,k1),y=find(rt,k2+1);
//	printf("find:%d %d %d %d\n",k1,k2,v[x],v[y]);
	splay(x,0);splay(y,x);
//	v[y]-=c;ans[id[y]]++;
//	printf("%d %d\n",rs(y),v[rs(y)]);
//	v[rs(y)]-=c,ans[id[rs(y)]]++,tag[rs(y)]-=c,tim[rs(y)]++;
//	printf("%d %d\n",rs(y),v[rs(y)]);
	dfs(y,c);
//	push_all(rt);
	f(o,1,top) {
		int now=st[o];
		int val=v[now],i=id[now];
//		push_all(rt);
//		printf("ins:%lld \n",v[now]);
		del(now);
		ins({val,i});
	}
	top=0;
}

signed main() {
    read(n);
    f(i,1,n) read(a[i].c),read(a[i].q);
    read(m);
    b[1]={-inf,0};b[m+2]={inf,0};
    f(i,2,m+1) read(b[i].v),b[i].id=i-1;
    sort(a+1,a+1+n,cmp1);
    sort(b+1,b+3+m,cmp2);
//    f(i,2,m+1) printf("%d ",b[i].v);
//    puts("");
    rt=build(1,m+2,0);
//    f(i,1,tot) printf("tr:%d %d %d\n",i,v[i],siz[i]);
    f(i,1,n) solve(a[i].c);
    push_all(rt);
	f(i,1,m) printf("%d ",ans[i]);
    return 0;
}

Katya and Segments Sets

题意

给定 \(n\) 个线段集合,共有 \(k\) 条线段,\(m\) 次询问,每次询问给定 \(a,b,x,r\),问对于所有编号在 \([a,b]\) 内的集合,是否都存在一条线段 \([l,r]\) 属于这个集合且 \(x\le l\le r\le y\),强制在线。

\(1\le n,m\le 10^5,1\le k\le 3\times 10^5\)

solution

\(S_i\) 为第 \(i\) 个集合,条件可以转化为,\((\min\limits_{i=a}^{b} (\max\limits_{[l,r]\in S_i, r\le y}l))\ge x\),直接对着集合做分块或者倍增可以做到 \(O(n\sqrt {n\log n})\) 或者 \(O(n\log ^2 n)\),但是都不算优秀。

我们换一下维度,对 \(r\) 扫描线,扫到 \(i\) 记录每个集合满足 \(r\le i\)\(l\) 的最大值,线段树维护区间最小值,做个可持久化,这样进行查询就变成 \(O(\log n)\) 的了。

时间复杂度 \(O(k\log n+m\log n)\)

Code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=3e5+10;
constexpr int P=4e7+10;
constexpr int V=1e9;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n,m,q,L,tot;
int rt[N],mx[N],mp[N];
struct Node{int l,r,p;}ps[N];
struct Line{int l,p;};
vector<Line> tmp[N];

struct Segment_Tree {
	struct Node {int ls,rs,mx;}tr[P];
	#define ls(x) tr[x].ls
	#define rs(x) tr[x].rs
	
	inline int newNode(int x) {
		tr[++tot]=tr[x];
		return tot;
	}
	
	inline void update(int x,int l,int r,int &now,int k) {
		now=newNode(now);
		if (l==r) return tr[now].mx=k,void();
		int mid=l+r>>1;
		if (x<=mid) update(x,l,mid,ls(now),k);
		else update(x,mid+1,r,rs(now),k);
		tr[now].mx=min(tr[ls(now)].mx,tr[rs(now)].mx);
	}
	
	inline int query(int x,int y,int l,int r,int now) {
		if (x<=l&&y>=r) return tr[now].mx;
		int mid=l+r>>1,res=inf;
		if (x<=mid) res=query(x,y,l,mid,ls(now));
		if (y>mid) res=min(res,query(x,y,mid+1,r,rs(now)));
		return res;
	}
}tr;

inline int find(int x) {
	int l=1,r=L,res=0;
	while (l<=r) {
		int mid=l+r>>1;
		if (mp[mid]<=x) res=mid,l=mid+1;
		else r=mid-1;
	}
	return res;
}

int main() {
	read(n);read(m);read(q);
	f(i,1,q) {
		int l,r,p;
		read(l);read(r);read(p);
		ps[i]={l,r,p};
		mp[++L]=r;
	}
	sort(mp+1,mp+1+L);
	L=unique(mp+1,mp+1+L)-mp-1;
	f(i,1,q) {
		int l=ps[i].l,r=ps[i].r,p=ps[i].p;
		r=lower_bound(mp+1,mp+1+L,r)-mp;
		tmp[r].push_back({l,p});
	}
	f(i,1,L) {
		rt[i]=rt[i-1];
		for (Line k:tmp[i]) if (k.l>mx[k.p]) {
			mx[k.p]=k.l;
			tr.update(k.p,1,n,rt[i],k.l);
		}
	}
	while (m--) {
		int l,r,x,y;
		read(x);read(y);read(l);read(r);
		int lst=tr.query(x,y,1,n,rt[find(r)]);
		puts(lst<l?"no":"yes");
		fflush(stdout);
	}
	return 0;
}

【模板】可持久化平衡树

solution

01 Trie 太牛了,直接上可持久化 01 Trie,时空复杂度 \(O(n\log V)\)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=5e5+10;
constexpr int P=2e7+10;

inline void read(int &x) {
	x=0;int f=1;
	char ch=getchar();
	while (ch<48) {if(ch=='-')f=-1;ch=getchar();} 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();x*=f;
}

int n,tot;
int rt[N];

namespace Trie {
	constexpr int D=36;
	constexpr ll MV=1e10;
	int val[P];
	struct Node{int ch[2],siz;}tr[P];
	
	inline int newNode(int x) {
		tr[++tot]=tr[x];
		return tot;
	}
	
	inline int insert(int now,ll x) {x+=MV;
		now=newNode(now);
		int ret=now;
		F(len,D,0) {
			int d=((x>>len)&1);
			tr[now].ch[d]=newNode(tr[now].ch[d]);
			tr[now].siz++;
			now=tr[now].ch[d];
		}
		tr[now].siz++;
		val[now]=x-MV;
		return ret;
	}
	
	inline int erase(int now,ll x) {x+=MV;
		int rnow=now;
		F(len,D,0) {
			int d=((x>>len)&1);
			rnow=tr[rnow].ch[d];
		}
		if (!tr[rnow].siz) return now;
		now=newNode(now);
		int ret=now;
		F(len,D,0) {
			int d=((x>>len)&1);
			tr[now].ch[d]=newNode(tr[now].ch[d]);
			tr[now].siz--;
			now=tr[now].ch[d];
		}
		tr[now].siz--;
		return ret;
	}
	
	inline int get_rank(int now,ll x) {x+=MV;
		int ret=0;
		F(len,D,0) {
			int d=((x>>len)&1);
			if (d) ret+=tr[tr[now].ch[0]].siz;
			now=tr[now].ch[d];
		}
		return ret+1;
	}
	
	inline int get_kth(int now,int k) {
		F(len,D,0) {
			int sz=tr[tr[now].ch[0]].siz;
			if (sz>=k) now=tr[now].ch[0];
			else k-=sz,now=tr[now].ch[1];
		}
		return val[now];
	}
	
	inline int get_pre(int now,ll x) {x+=MV;
		int pos=-1,ld=0;
		F(len,D,0) {
			int d=((x>>len)&1);
			if (d&&tr[tr[now].ch[0]].siz) pos=tr[now].ch[0],ld=len-1;
			now=tr[now].ch[d];
		}
		if (pos==-1) return -(1ll<<31)+1;
		now=pos;
		F(len,ld,0) {
			if (tr[tr[now].ch[1]].siz) now=tr[now].ch[1];
			else now=tr[now].ch[0];
		}
		return val[now];
	}
	
	inline int get_nxt(int now,ll x) {x+=MV;
		int pos=-1,ld=0;
		F(len,D,0) {
			int d=((x>>len)&1);
			if (!d&&tr[tr[now].ch[1]].siz) pos=tr[now].ch[1],ld=len-1;
			now=tr[now].ch[d];
		}
		if (pos==-1) return (1ll<<31)-1;
		now=pos;
		F(len,ld,0) {
			if (tr[tr[now].ch[0]].siz) now=tr[now].ch[0];
			else now=tr[now].ch[1];
		}
		return val[now];
	}
};

int main() {
	read(n);
	f(i,1,n) {
		int lst,op,x;
		read(lst);read(op);read(x);
		rt[i]=rt[lst];
		if (op==1) rt[i]=Trie::insert(rt[i],x);
		else if (op==2) rt[i]=Trie::erase(rt[i],x);
		else if (op==3) printf("%d\n",Trie::get_rank(rt[i],x));
		else if (op==4) printf("%d\n",Trie::get_kth(rt[i],x));
		else if (op==5) printf("%d\n",Trie::get_pre(rt[i],x));
		else printf("%d\n",Trie::get_nxt(rt[i],x));
	}
	return 0;
}
posted @ 2026-07-19 11:42  Ff472130  阅读(3)  评论(0)    收藏  举报