MX 暑假集训 7.19
何意味,打开题单发现有一半的题都做过。
一些板子就不讲了。
[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;
}

浙公网安备 33010602011771号