数据结构杂题选做
楼房重建
先搞清楚题目要求的是什么。令 \(k_0=0,k_i=\frac{h_i}{i}\),则题目求的一个从 \(0\) 开始的单调上升序列的长度减一。
最暴力的做法就是直接维护上升的序列,修改后从修改处开始重新维护,但时间复杂度不对,考虑优化。
先忽略从 \(0\) 开始的限制条件。
令 \(mx_{\left[l,r\right]}\) 表示区间 \(\left[l,r\right]\) 内的最大值,对于区间 \(\left[l,k\right]\) 和 \(\left[k+1,r\right]\) 可以发现当 \(mx_{\left[l,k\right]}<mx_{\left[k+1,r\right]}\) 时,区间 \(\left[l,r\right]\) 的答案是区间 \(\left[l,k\right]\) 的答案合并而来的。对于不满足上述条件的区间,可以在 \(\left[k+1,r\right]\) 上找到第一个大于 \(mx_{\left[l,k\right]}\) 的数的位置 \(x\),得到 \(\left[x,r\right]\) 的答案,区间 \(\left[l,r\right]\) 的答案也可以转移得到。既然可以区间合并,考虑线段树。
对于区间 \(\left[l,r\right]\),若 \(l=r\),则有值答案赋为 \(1\),没值答案赋为 \(0\)。否则考虑两个区间最大值之间的关系,按照上述方法合并,只需要找到 \(x\) 就可以了。对于 \(\left[x,y\right]\),因为所有子区间的答案都知道了,若左区间最大值大于要求的值,则右区间对 \(\left[x,y\right]\) 的答案的贡献可以直接计入,再往左区间递归寻找,否则往右区间递归寻找。每次修改之后重新维护答案即可,答案为区间 \(\left[1,n\right]\) 的答案。
update on 2023.4.9
有人告诉我原来这个就是兔队线段树,get新知识了
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define LD long double
#define pdi pair<double,int>
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define eps 1e-9
using namespace std;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int N=2e5+10;
int n,q;
namespace Seg_Tree{
struct node{
int tot;
LD mx;
}tr[N<<2];
int ls(int p){
return p<<1;
}
int rs(int p){
return p<<1|1;
}
int query(int p,int l,int r,LD mx){
if(l==r){
return tr[p].mx>mx;
}
int mid=(l+r)>>1;
if(tr[ls(p)].mx<=mx){
return query(rs(p),mid+1,r,mx);
}
else{
return query(ls(p),l,mid,mx)+tr[p].tot-tr[ls(p)].tot;
}
}
void update(int p,int l,int r,int pos,LD val){
if(l==r){
tr[p].tot=1;
tr[p].mx=val;
return;
}
int mid=(l+r)>>1;
if(pos<=mid){
update(ls(p),l,mid,pos,val);
}
else{
update(rs(p),mid+1,r,pos,val);
}
tr[p].mx=max(tr[ls(p)].mx,tr[rs(p)].mx);
tr[p].tot=tr[ls(p)].tot+query(rs(p),mid+1,r,tr[ls(p)].mx);
}
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
read(n),read(q);
while(q--){
int pos,h;
read(pos),read(h);
Seg_Tree::update(1,1,n,pos,1.0L*h/pos);
write_endl(Seg_Tree::tr[1].tot);
}
return 0;
}
[FJOI2016]神秘数
先忽略其它条件,考虑一个集合 \(s\) 怎么做。若 \(x\) 为当前可以得到的最大值,往 \(s\) 中加入一个数 \(y\),当且仅当 \(y\le x\) 时,\(x\) 会变大。
题目就转化为:
- 将 \(ans\) 赋为 \(1\)。
- 求出区间内值在 \(1-ans\) 内所有数的和 \(res\)。
- 将 \(ans\) 赋为 \(res+1\)
- 重复操作 \(2,3\)。
模拟可知最劣情况增量是一个斐波那契数列,最多操作次数在 \(log\) 级别。
用可持久化线段树维护即可。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pdi pair<double,int>
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define eps 1e-9
using namespace std;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int N=1e5+10,Lg=31,inf=1e9;
int n,q,cnt,rt[N];
namespace Seg_Tree{
struct node{
int ch[2],val;
}tr[N*Lg];
#define ls(p) tr[p].ch[0]
#define rs(p) tr[p].ch[1]
void update(int &p,int pre,int l,int r,int pos,int val){
p=++cnt;
tr[p]=tr[pre];
tr[p].val+=val;
if(l==r){
return;
}
int mid=(l+r)>>1;
if(pos<=mid){
update(ls(p),ls(pre),l,mid,pos,val);
}
else{
update(rs(p),rs(pre),mid+1,r,pos,val);
}
}
int query(int a,int b,int l,int r,int q_l,int q_r){
if(q_l<=l&&r<=q_r){
return tr[a].val-tr[b].val;
}
int mid=(l+r)>>1;
int res=0;
if(q_l<=mid){
res+=query(ls(a),ls(b),l,mid,q_l,q_r);
}
if(q_r>mid){
res+=query(rs(a),rs(b),mid+1,r,q_l,q_r);
}
return res;
}
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
int n;
read(n);
for(int i=1;i<=n;i++){
int x;
read(x);
Seg_Tree::update(rt[i],rt[i-1],1,inf,x,x);
}
read(q);
while(q--){
int l,r;
read(l),read(r);
int ans=1;
while(1){
int res=Seg_Tree::query(rt[r],rt[l-1],1,inf,1,ans);
if(res>=ans){
ans=res+1;
}
else{
break;
}
}
write_endl(ans);
}
return 0;
}
窗口的星星
让我们求一个矩形内最多包含多少个关键点还是太难了,转化一下题意,将一个关键点表示为一个可以包含关键点的矩形,求一个点最多被多少个矩形覆盖。
因为边界是不能包含在内的,所以将所有的格点全部向右平移半格,再向上平移半格,这样包含一个关键点 \((x,y)\) 的矩形就可以表示为 \([x,x+w-1],[y,y+h-1]\)。用扫描线随便维护一下就可以知道一个点最多可以被多少矩形覆盖。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define int long long
#define pdi pair<double,int>
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define eps 1e-9
using namespace std;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int N=1e5+10;
int n,w,h,x[N],cntx,cnt;
struct node{
int xl,xr,y,val;
}mat[N];
bool cmp(node x,node y){
return x.y==y.y?x.val>y.val:x.y<y.y;
}
int ans[N<<2],tag[N<<2];
int ls(int p){
return p<<1;
}
int rs(int p){
return p<<1|1;
}
void push_up(int p){
ans[p]=max(ans[ls(p)],ans[rs(p)]);
}
void build(int p,int l,int r){
ans[p]=tag[p]=0;
if(l==r){
return;
}
int mid=(l+r)>>1;
build(ls(p),l,mid);
build(rs(p),mid+1,r);
}
void push_down(int p){
ans[ls(p)]+=tag[p];
tag[ls(p)]+=tag[p];
ans[rs(p)]+=tag[p];
tag[rs(p)]+=tag[p];
tag[p]=0;
}
void update(int p,int l,int r,int q_l,int q_r,int val){
if(q_l<=l&&r<=q_r){
ans[p]+=val;
tag[p]+=val;
return;
}
int mid=(l+r)>>1;
push_down(p);
if(q_l<=mid){
update(ls(p),l,mid,q_l,q_r,val);
}
if(q_r>mid){
update(rs(p),mid+1,r,q_l,q_r,val);
}
push_up(p);
}
void solve(){
read(n),read(w),read(h);
cntx=cnt=0;
for(int i=1;i<=n;i++){
int X,Y,val;
read(X),read(Y),read(val);
mat[++cnt].xl=X,mat[cnt].xr=X+w-1,mat[cnt].y=Y,mat[cnt].val=val;
mat[++cnt].xl=X,mat[cnt].xr=X+w-1,mat[cnt].y=Y+h-1,mat[cnt].val=-val;
x[++cntx]=X,x[++cntx]=X+w-1;
}
sort(x+1,x+cntx+1);
cntx=unique(x+1,x+cntx+1)-x-1;
sort(mat+1,mat+cnt+1,cmp);
build(1,1,cntx);
int Ans=0;
for(int i=1;i<=cnt;i++){
mat[i].xl=lower_bound(x+1,x+cntx+1,mat[i].xl)-x;
mat[i].xr=lower_bound(x+1,x+cntx+1,mat[i].xr)-x;
update(1,1,cntx,mat[i].xl,mat[i].xr,mat[i].val);
Ans=max(Ans,ans[1]);
}
write_endl(Ans);
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
int t;
read(t);
while(t--){
solve();
}
return 0;
}
火星商店问题
先考虑不新添货物,即没有时间线该怎么做。可以每个点建一棵可持久化01trie,每次从上一个点的01trie上修改。询问时考虑差分,只访问在 \(siz_r-siz_{l-1}>0\) 的点。
根据一个01trie的一个性质,\(\max(ans_{t1},ans_{t2})=ans_{t1\cup t2}\),\(t1,t2,t1\cup t2\) 均代表一棵01trie。
所以可以将询问线段树分治,将修改按照商店的编号排序,并按时间整体二分。对于一个整体二分的时间段 \([l,r]\),此时它对应的修改是按照商店编号有序的,同时对应,可以通过二分得到对一次询问有影响的修改有哪些,在对应区间上的答案可能就是最终答案。又因为一个时间点和一个修改是一一对应的,所以可以选择每次求答案时选择暴力重建可持久化01trie,总复杂度为 \(O(n\log^2n)\)。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pdi pair<double,int>
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define eps 1e-9
using namespace std;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int N=1e5+10,Lg=18;
int cnt,n,m,rt[N],ans[N];
vector<int>num[N<<2];
namespace trie{
struct node{
int ch[2],cnt;
}tr[N*(Lg+5)];
void ins(int &p,int pre,int val){
p=++cnt;
int now=p;
for(int i=Lg;i>=0;i--){
int opt=val>>i&1;
tr[now].ch[opt^1]=tr[pre].ch[opt^1];
tr[now].ch[opt]=++cnt;
now=tr[now].ch[opt];
pre=tr[pre].ch[opt];
tr[now].cnt=tr[pre].cnt+1;
}
}
int query(int l,int r,int val){
int res=0;
for(int i=Lg;i>=0;i--){
int opt=val>>i&1;
if(tr[tr[r].ch[opt^1]].cnt-tr[tr[l].ch[opt^1]].cnt>0){
l=tr[l].ch[opt^1];
r=tr[r].ch[opt^1];
res+=(1<<i);
}
else{
l=tr[l].ch[opt],r=tr[r].ch[opt];
}
}
return res;
}
}
struct node{
int st,ed,timel,timer,x;
}s[N];
struct query{
int s,v,t;
}q[N],tmp1[N],tmp2[N];
int ls(int p){
return p<<1;
}
int rs(int p){
return p<<1|1;
}
void update(int p,int l,int r,int q_l,int q_r,int id){
if(q_l>q_r){
return;
}
if(q_l<=l&&r<=q_r){
num[p].pb(id);
return;
}
int mid=(l+r)>>1;
if(q_l<=mid){
update(ls(p),l,mid,q_l,q_r,id);
}
if(q_r>mid){
update(rs(p),mid+1,r,q_l,q_r,id);
}
}
int stk[N],top;
void calc(int p,int l,int r){
top=cnt=0;
for(int i=l;i<=r;i++){
stk[++top]=q[i].s;
trie::ins(rt[top],rt[top-1],q[i].v);
}
for(auto x:num[p]){
int l=lower_bound(stk+1,stk+top+1,s[x].st)-stk-1,r=upper_bound(stk+1,stk+top+1,s[x].ed)-stk-1;
ans[x]=max(ans[x],trie::query(rt[l],rt[r],s[x].x));
}
}
void solve(int p,int l,int r,int L,int R){
if(L>R){
return;
}
int cnt1=0,cnt2=0;
calc(p,L,R);
if(l==r){
return;
}
int mid=(l+r)>>1,idx1=0,idx2=0;
for(int i=L;i<=R;i++){
if(q[i].t<=mid){
tmp1[++idx1]=q[i];
}
else{
tmp2[++idx2]=q[i];
}
}
for(int i=1;i<=idx1;i++){
q[i+L-1]=tmp1[i];
}
for(int i=1;i<=idx2;i++){
q[i+L-1+idx1]=tmp2[i];
}
solve(ls(p),l,mid,L,L+idx1-1);
solve(rs(p),mid+1,r,L+idx1,R);
}
bool cmp(query x,query y){
return x.s<y.s;
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
read(n),read(m);
for(int i=1;i<=n;i++){
int x;
read(x);
trie::ins(rt[i],rt[i-1],x);
}
int cnt1=0,cnt2=0;
for(int i=1;i<=m;i++){
int type,l,r,x,y;
read(type);
if(type==0){
read(x),read(y);
q[++cnt1]=(query){x,y,cnt1};
}
else{
read(l),read(r),read(x),read(y);
ans[++cnt2]=trie::query(rt[l-1],rt[r],x);
s[cnt2]=(node){l,r,max(1,cnt1-y+1),cnt1,x};
}
}
for(int i=1;i<=cnt2;i++){
update(1,1,cnt1,s[i].timel,s[i].timer,i);
}
sort(q+1,q+cnt1+1,cmp);
solve(1,1,cnt1,1,cnt1);
for(int i=1;i<=cnt2;i++){
write_endl(ans[i]);
}
return 0;
}
[SDOI2016]游戏
先考虑树上求距离的公式,令 \(dis_u\) 表示 \(1\) 到 \(u\) 的距离,\(d_{u,v}\) 表示 \(u\) 到 \(v\) 的距离,\(d_{u,v}=dis_u+dis_v-2\times dis_{lca(u,v)}\)。可以发现原路径可以化为从 \(u\) 到 \(\operatorname{lca}(u,v)\) 和 \(\operatorname{lca}(u,v)\) 到 \(v\) 两条路径。这两条路径的方程是不同的,但同一路径上所有点的方程是相同的。
在路径 \(u\) 到 \(\operatorname{lca}(u,v)\) 的点 \(x\) 到 \(u\) 的距离为 \(dis_u-dis_x\),对应的权值方程 \(k\times(dis_u-dis_x)+b\),即 \(k\times dis_u+b-k\times dis_x\),将 \(dis_x\) 看作 \(x\),\(-k\) 看作 \(k\),\(k\times dis_u+b\) 看作 \(b\),这就是路径的权值方程。在另一条路径上的点的权值方程为 \(k\times (dis_u-2\times dis_{lca(u,v)})+b+k\times dis_x\),转成和上述方程一样的形式,可以发现我们将 \(dis_x\) 均视作了 \(x\),可以用李超树维护了,剩下的树剖加李超树维护区间最值。
因为李超树上每个节点存的线段一定包含该区间,所以子树内存的线段的最小值为各线段端点值取 \(\min\)。需要注意的是第 \(0\) 条线段要赋为 \(y=0x+inf\),区间询问时不止要处理分出来的 \(\log\) 个区间,也要记得处理其它有交集的区间标记永久化的线段的答案。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define int long long
#define pii pair<int,int>
#define pdi pair<double,int>
#define pb push_back
#define eps 1e-9
#define mp make_pair
using namespace std;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int N=1e5+10,Lg=20,inf=123456789123456789;
int n,q,fa[N],siz[N],dep[N],dis[N],heavy[N],dfn[N],top[N],rdfn[N],idx;
vector<pii>e[N];
int tot,rt;
struct Line{
int k,b;
Line(){
k=0,b=inf;
}
}seg[N*2];
namespace Seg_Tree{
int cnt;
struct node{
int ch[2],mn,id;
node(){
ch[0]=ch[1]=id=0;
mn=inf;
}
}tr[N*Lg];
#define ls(p) tr[p].ch[0]
#define rs(p) tr[p].ch[1]
int get(int pos,int sid){
return seg[sid].k*dis[rdfn[pos]]+seg[sid].b;
}
void push_up(int p,int l,int r){
tr[p].mn=min(tr[p].mn,min(get(l,tr[p].id),get(r,tr[p].id)));
tr[p].mn=min(tr[p].mn,min(tr[ls(p)].mn,tr[rs(p)].mn));
}
void change(int &p,int l,int r,int u){
if(!p){
p=++cnt;
}
int mid=(l+r)>>1,&v=tr[p].id;
if(get(mid,v)>get(mid,u)){
swap(u,v);
}
if(get(l,v)>get(l,u)){
change(ls(p),l,mid,u);
}
if(get(r,v)>get(r,u)){
change(rs(p),mid+1,r,u);
}
push_up(p,l,r);
}
void update(int &p,int l,int r,int q_l,int q_r,int u){
if(!p){
p=++cnt;
}
if(q_l<=l&&r<=q_r){
change(p,l,r,u);
return;
}
int mid=(l+r)>>1;
if(q_l<=mid){
update(ls(p),l,mid,q_l,q_r,u);
}
if(q_r>mid){
update(rs(p),mid+1,r,q_l,q_r,u);
}
push_up(p,l,r);
}
int query(int p,int l,int r,int q_l,int q_r){
if(!p){
return inf;
}
if(q_l<=l&&r<=q_r){
return tr[p].mn;
}
int mid=(l+r)>>1,res=min(get(max(q_l,l),tr[p].id),get(min(q_r,r),tr[p].id));
if(q_l<=mid){
res=min(res,query(ls(p),l,mid,q_l,q_r));
}
if(q_r>mid){
res=min(res,query(rs(p),mid+1,r,q_l,q_r));
}
return res;
}
}
void make_tree(int u,int father){
fa[u]=father;
siz[u]=1;
dep[u]=dep[father]+1;
for(auto x:e[u]){
int v=x.first,w=x.second;
if(v==father){
continue;
}
dis[v]=dis[u]+w;
make_tree(v,u);
siz[u]+=siz[v];
if(siz[v]>siz[heavy[u]]){
heavy[u]=v;
}
}
}
void dfs(int u,int topf){
top[u]=topf;
dfn[u]=++idx;
rdfn[idx]=u;
if(heavy[u]){
dfs(heavy[u],topf);
}
for(auto x:e[u]){
int v=x.first;
if(v==fa[u]||v==heavy[u]){
continue;
}
dfs(v,v);
}
}
int LCA(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]){
swap(u,v);
}
u=fa[top[u]];
}
if(dep[u]>dep[v]){
swap(u,v);
}
return u;
}
void update(int u,int v){
while(top[u]!=top[v]){
Seg_Tree::update(rt,1,n,dfn[top[u]],dfn[u],tot);
u=fa[top[u]];
}
Seg_Tree::update(rt,1,n,dfn[v],dfn[u],tot);
}
int query(int u,int v){
int res=inf;
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]){
swap(u,v);
}
res=min(res,Seg_Tree::query(rt,1,n,dfn[top[u]],dfn[u]));
u=fa[top[u]];
}
if(dep[u]>dep[v]){
swap(u,v);
}
res=min(res,Seg_Tree::query(rt,1,n,dfn[u],dfn[v]));
return res;
}
void solve(){
read(n),read(q);
for(int i=1,u,v,w;i<n;i++){
read(u),read(v),read(w);
e[u].pb(mp(v,w));
e[v].pb(mp(u,w));
}
make_tree(1,0);
dfs(1,1);
while(q--){
int opt,s,t,k,b;
read(opt),read(s),read(t);
if(opt==1){
read(k),read(b);
int lca=LCA(s,t);
seg[++tot].k=-k;
seg[tot].b=b+k*dis[s];
update(s,lca);
seg[++tot].k=k;
seg[tot].b=b+k*(dis[s]-dis[lca]*2);
update(t,lca);
}
else{
write_endl(query(s,t));
}
}
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
int t=1;
while(t--){
solve();
}
return 0;
}
Shortest Path Queries
先考虑不带加边或删边的情况。先求出一条从 \(x\) 到 \(y\) 的路径的异或和 \(val\),显然的是一条路径异或上一个环,就会变成另一条路径。所以我们将所有的环的异或和求出来放入线性基,求出 \(val\) 和这些环异或和的异或最小值在线性基上贪心,如果 \(val\) 从高往低第 \(i\) 位为 \(1\),且线性基第 \(i\) 位有值,那么直接异或。根据线性基的性质,这是一定变小的。
现在有了加边删边操作,因为线性基不方便删除,所以我们可以使用线段树分治将删除转为撤销。又因为要维护环,所以我们还需要可撤销并查集来维护连通性问题。下面仅讨论如何用并查集维护环的异或和。令 \(dis_u\) 表示点 \(u\) 到并查集的根的距离的异或和,当要加入 \((u,v,w)\) 这条边且 \(u,v\) 已经在同一连通块时,这已经是一个环了,环的异或和为 \(dis_u\oplus dis_v\oplus w\),将它插入到线性基中。当 \(u,v\) 分属子树 \(fu\) 和 \(fv\) 时,我们要考虑如何维护 \(dis\) 这个信息。因为要带撤销操作,所以路径压缩是不在我们考虑范围内的,只能考虑按秩合并。假定 \(fu\) 为大的连通块,我们想要 \(u\) 到 \(v\) 的路径异或和为 \(w\),但我们的边只能加在 \(fu\) 和 \(fv\) 之间,列一个等式 \(w_{u,v}=dis_u\oplus dis_v\oplus w_{fu,fv}\),化一下可以得到 \(w_{fu,fv}=w_{u,v}\oplus dis_u\oplus dis_v\),于是我们在 \(fu\) 和 \(fv\) 之间加上一条边权为 \(w_{u,v}\oplus dis_u\oplus dis_v\) 的边。
现在问题来了,对于环套环的情况,这样做是对的吗?显然正确,因为两个环各异或一遍,相当于异或了剩下的一个环,所以我们能够保证所有环都能够拓展原来的路径,也就转化成了我们前面不带加边或删边的情况。
本题还有道类似的题叫做八纵八横。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
using namespace std;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int Lg=31,N=4e5+10,inf=1e9;
#define bit bitset<Lg+5>
int n,m,Time[N],tot,q,flag[N];
unordered_map<int,int>id[N];
struct edge{
int u,v;
bit w;
}e[N];
struct query{
int u,v;
}Query[N];
namespace Linear_Basis{
bit p[Lg+5];
int stk[N],top;
void insert(bit x){
for(int i=Lg;i>=0;i--){
if(x[i]){
if(p[i].none()){
stk[++top]=i;
p[i]=x;
return;
}
x^=p[i];
}
}
}
bit query(bit ans){
for(int i=Lg;i>=0;i--){
if(ans[i]&&p[i].any()){
ans^=p[i];
}
}
return ans;
}
void del(int pos){
while(top!=pos){
p[stk[top--]].reset();
}
}
}
namespace dsu{
int fa[N],siz[N],top;
bit dis[N];
struct node{
int u,v,siz;
}stk[N];
void init(){
for(int i=1;i<=n;i++){
fa[i]=i;
siz[i]=1;
}
}
bit get_dis(int u){
if(fa[u]==u){
return dis[u];
}
return dis[u]^get_dis(fa[u]);
}
int getfa(int u){
if(fa[u]!=u){
return getfa(fa[u]);
}
return u;
}
void merge(int u,int v,bit w){
int fu=getfa(u),fv=getfa(v);
if(fu==fv){
Linear_Basis::insert(get_dis(u)^get_dis(v)^w);
return;
}
if(siz[fu]<siz[fv]){
swap(fu,fv);
}
stk[++top]=node{fu,fv,siz[fu]};
dis[fv]=get_dis(u)^get_dis(v)^w;
fa[fv]=fu;
siz[fu]+=siz[fv];
}
void del(int pos){
while(top!=pos){
fa[stk[top].v]=stk[top].v;
siz[stk[top].u]=stk[top].siz;
dis[stk[top].v].reset();
top--;
}
}
}
namespace Seg_Tree{
vector<int>num[N<<2];
int ls(int p){
return p<<1;
}
int rs(int p){
return p<<1|1;
}
void update(int p,int l,int r,int q_l,int q_r,int id){
if(q_l<=l&&r<=q_r){
num[p].pb(id);
return;
}
int mid=(l+r)>>1;
if(q_l<=mid){
update(ls(p),l,mid,q_l,q_r,id);
}
if(q_r>mid){
update(rs(p),mid+1,r,q_l,q_r,id);
}
}
void solve(int p,int l,int r){
int tag1=Linear_Basis::top,tag2=dsu::top;
for(auto x:num[p]){
dsu::merge(e[x].u,e[x].v,e[x].w);
}
if(l==r){
if(flag[l]){
int u=Query[l].u,v=Query[l].v;
int ans=Linear_Basis::query(dsu::get_dis(u)^dsu::get_dis(v)).to_ulong();
write_endl(ans);
}
}
else{
int mid=(l+r)>>1;
solve(ls(p),l,mid);
solve(rs(p),mid+1,r);
}
Linear_Basis::del(tag1);
dsu::del(tag2);
}
}
void solve(){
read(n),read(m);
for(int i=1,u,v,w;i<=m;i++){
read(u),read(v),read(w);
e[++tot].u=u;
e[tot].v=v;
e[tot].w=w;
id[v][u]=id[u][v]=tot;
}
dsu::init();
read(q);
for(int i=1;i<=q;i++){
int opt,u,v,w;
read(opt);
if(opt==1){
read(u),read(v),read(w);
e[++tot].u=u;
e[tot].v=v;
e[tot].w=w;
Time[tot]=i;
id[u][v]=id[v][u]=tot;
}
else if(opt==2){
read(u),read(v);
int idx=id[u][v];
Seg_Tree::update(1,0,q,Time[idx],i-1,idx);
Time[idx]=inf;
}
else{
read(u),read(v);
Query[i].u=u;
Query[i].v=v;
flag[i]=1;
}
}
for(int i=1;i<=tot;i++){
if(Time[i]<inf){
Seg_Tree::update(1,0,q,Time[i],q,i);
}
}
Seg_Tree::solve(1,0,q);
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
int t=1;
while(t--){
solve();
}
return 0;
}
[AHOI2013] 连通图
将删边变为加边,找到一条边存在的时间区间,线段树分治后用并查集判连通块数是否为 \(1\) 即可,很板。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pii pair<int,int>
#define pdi pair<double,int>
#define pb push_back
#define eps 1e-9
#define mp make_pair
using namespace std;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int N=2e5+10;
vector<int>num[N<<2],pos[N<<1];
struct edge{
int u,v;
}e[N<<1];
int n,m,ans[N];
namespace dsu{
struct node{
int u,v,siz;
};
int siz[N],fa[N],top,flag;
node stk[N];
void init(int n){
for(int i=1;i<=n;i++){
fa[i]=i;
siz[i]=1;
}
flag=0;
}
int getfa(int x){
if(fa[x]==x){
return x;
}
return getfa(fa[x]);
}
void merge(int u,int v){
int fu=getfa(u),fv=getfa(v);
if(fu==fv){
return;
}
if(siz[fu]<siz[fv]){
stk[++top].u=fv;
stk[top].v=fu;
stk[top].siz=siz[fv];
siz[fv]+=siz[fu];
fa[fu]=fv;
if(siz[fv]==n){
flag=1;
}
}
else{
stk[++top].u=fu;
stk[top].v=fv;
stk[top].siz=siz[fu];
siz[fu]+=siz[fv];
fa[fv]=fu;
if(siz[fu]==n){
flag=1;
}
}
}
void del(int pos){
while(top!=pos){
fa[stk[top].v]=stk[top].v;
siz[stk[top].u]=stk[top].siz;
top--;
}
}
}
int ls(int p){
return p<<1;
}
int rs(int p){
return p<<1|1;
}
void ins(int p,int l,int r,int q_l,int q_r,int id){
if(q_l<=l&&r<=q_r){
num[p].pb(id);
return;
}
int mid=(l+r)>>1;
if(q_l<=mid){
ins(ls(p),l,mid,q_l,q_r,id);
}
if(q_r>mid){
ins(rs(p),mid+1,r,q_l,q_r,id);
}
}
void work(int p,int l,int r){
int pos=dsu::top,flag=dsu::flag;
for(auto x:num[p]){
dsu::merge(e[x].u,e[x].v);
}
if(l==r){
if(dsu::flag){
ans[l]=1;
}
else{
ans[l]=-1;
}
}
else{
int mid=(l+r)>>1;
work(ls(p),l,mid);
work(rs(p),mid+1,r);
}
dsu::flag=flag;
dsu::del(pos);
}
void solve(){
read(n),read(m);
dsu::init(n);
for(int i=1;i<=m;i++){
read(e[i].u),read(e[i].v);
}
int q;
read(q);
for(int i=1;i<=q;i++){
int sum,x;
read(sum);
while(sum--){
read(x);
pos[x].pb(i);
}
}
for(int i=1;i<=m;i++){
pos[i].pb(q+1);
int lst=1;
for(int j=0;j<pos[i].size();j++){
if(lst<=pos[i][j]-1){
ins(1,1,q,lst,pos[i][j]-1,i);
}
lst=pos[i][j]+1;
}
}
work(1,1,q);
for(int i=1;i<=q;i++){
if(ans[i]==1){
puts("Connected");
}
else if(ans[i]==-1){
puts("Disconnected");
}
}
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
int t=1;
while(t--){
solve();
}
return 0;
}
COT - Count on a tree
树上路径的另一种理解为树上区间,区间求第 \(k\) 小,容易想到主席树。我们可以每个点在父亲的版本上修改,最后答案为 \(u\) 点的版本加上 \(v\) 点的版本减去 \(lca\) 的版本和 \(fa_{lca}\) 的版本的答案。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pii pair<int,int>
#define pdi pair<double,int>
#define pb push_back
#define eps 1e-9
#define mp make_pair
using namespace std;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int N=1e5+10,Lg=20;
int val[N],a[N],n,q,m;
namespace Seg_Tree{
int cnt;
struct node{
int ch[2],siz;
}tr[N*Lg];
#define ls(p) tr[p].ch[0]
#define rs(p) tr[p].ch[1]
void update(int &p,int pre,int l,int r,int pos){
p=++cnt;
tr[p]=tr[pre];
tr[p].siz++;
if(l==r){
return;
}
int mid=(l+r)>>1;
if(pos<=mid){
update(ls(p),ls(pre),l,mid,pos);
}
else{
update(rs(p),rs(pre),mid+1,r,pos);
}
}
int query(int a,int b,int c,int d,int l,int r,int k){
if(l==r){
return val[l];
}
int x=tr[ls(a)].siz+tr[ls(b)].siz-tr[ls(c)].siz-tr[ls(d)].siz,mid=(l+r)>>1;
if(x>=k){
return query(ls(a),ls(b),ls(c),ls(d),l,mid,k);
}
else{
return query(rs(a),rs(b),rs(c),rs(d),mid+1,r,k-x);
}
}
}
int head[N],tot;
int fa[N],siz[N],dep[N],heavy[N],rt[N],top[N];
struct edge{
int v,nxt;
}e[N<<1];
void add(int u,int v){
e[++tot].v=v;
e[tot].nxt=head[u];
head[u]=tot;
}
void add_e(int u,int v){
add(u,v);
add(v,u);
}
void make_tree(int u,int father){
Seg_Tree::update(rt[u],rt[father],1,m,a[u]);
fa[u]=father;
siz[u]=1;
dep[u]=dep[fa[u]]+1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(v==father){
continue;
}
make_tree(v,u);
siz[u]+=siz[v];
if(siz[v]>siz[heavy[u]]){
heavy[u]=v;
}
}
}
void dfs(int u,int topf){
top[u]=topf;
if(heavy[u]){
dfs(heavy[u],topf);
}
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(v==heavy[u]||v==fa[u]){
continue;
}
dfs(v,v);
}
}
int LCA(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]){
swap(u,v);
}
u=fa[top[u]];
}
if(dep[u]>dep[v]){
swap(u,v);
}
return u;
}
void solve(){
read(n),read(q);
for(int i=1;i<=n;i++){
read(a[i]);
val[i]=a[i];
}
for(int i=1;i<n;i++){
int u,v;
read(u),read(v);
add_e(u,v);
}
sort(val+1,val+n+1);
m=unique(val+1,val+n+1)-val-1;
for(int i=1;i<=n;i++){
a[i]=lower_bound(val+1,val+m+1,a[i])-val;
}
make_tree(1,0);
dfs(1,1);
while(q--){
int u,v,k,lca;
read(u),read(v),read(k);
lca=LCA(u,v);
int ans=Seg_Tree::query(rt[u],rt[v],rt[lca],rt[fa[lca]],1,m,k);
write_endl(ans);
}
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
int t=1;
while(t--){
solve();
}
return 0;
}

浙公网安备 33010602011771号