P9175 [COCI2022-2023#4] Mreža 题解
前言
我发现,有整体二分与主席树的地方总有莫队(不是那个莫队,是那个莫队)。
知识点
(树上)倍增,(树上)莫队,树状数组(“树”含量满满),分块。
题意分析
给定一棵树,每条边有一个权值 \(v\),以及可以用一个花费 \(c\) 将它变成更大的权值 \(s\)。再给定一些询问,问在总花费不超过一个值 \(e\) 的情况下进行改变,节点 \(a\) 与 \(b\) 之间的边的权值最小值最大为多少。
思路分析
对于每一次询问,我们肯定是节点 \(a\) 与 \(b\) 之间边权最小的边不停更新,直到不能更新。基于这个思路,Subtask 1 已经可以解决了。
但是下一步怎么优化呢?我们可以想到,可以先将原边权离散化,然后根据离散化后的值排序,然后每次查询就变成了在这个数组上从大到小遍历,加上在节点 \(a\) 与 \(b\) 之间路径上的边的权值,直到总和大于 \(e\),这个过程我们可以用树状数组解决,更新时间复杂度为 \(O(\log_2{n})\),然后可以树状数组上倍增查询,时间复杂度也为 \(O(\log_2{n})\)(你可以换用分块,这样就可以变成更新时间复杂度为 \(O(1)\),查询时间复杂度为 \(O(\sqrt{n})\),总体理论时间复杂度会更加正确)。
然后 Subtask 2 的部分就直接套莫队,也紧接着迎刃而解了。
剩下 Subtask 3,我们在 Subtask 2 的 基础上,用括号序把树上问题转换成序列问题,稍加改进,整个问题就解决了。
最后还有一些细节:
- 对于每一次询问,答案上界是节点 \(a\) 与 \(b\) 之间所有更新后的边权最小值,这个可以用树上倍增解决。
- 对于每一次询问,当 \(e\) 大于更新节点 \(a\) 与 \(b\) 之间所有边权的总花费,就不需要在树状数组中查询了,否则可能导致错误。
- 转换成括号序后,节点 \(a\) 与 \(b\) 为祖孙关系时询问区间要特殊处理。
关于复杂度
空间复杂度上主要瓶颈是倍增数组,为 \(O(n\log_2{n})\)。
那么我们来看时间复杂度。
对于莫队部分,这里简单分析一下:
设 \(S\) 为单块大小,那么块数为 \(O(\frac{n}{S})\) 级别。每次左端点最大移动次数级别为 \(O(S)\);在左端点在同一块内时,右端点最大移动次数级别为 \(O(n)\)。
所以莫队部分左右端点(指针)移动次数大概为 \(O(qS+\frac{n^2}{S})\),在 \(S\) 取 \(\frac{n}{\sqrt{q}}\) 的时候最小平衡到 \(O(n\sqrt{q})\)。
然后再分别讨论树状树组与分块的时间复杂度:
- 树状树组:单次更新 \(O(\log_2{n})\),查询 \(O(\log_2{n})\),总时间复杂度在 \(O(n\log_2{n}\sqrt{q}+q\log_2{n})\) 左右;
- 分块:单次更新 \(O(1)\),查询 \(O(\sqrt{n})\),总时间复杂度在 \(O(n\sqrt{q}+q\sqrt{n})\) 左右。
首先分块肯定没问题,但是树状数组呢,它的理论总时间复杂度好像不太行啊?
我们注意到莫队这类题目的时间复杂度一般都是讨论最坏情况,所以基本都是跑不满的,再加之这还是个树上莫队,真的要卡它也很难。就算是链,我们在块长不一样的情况下,运行的情况也是不一样的。如果还不够的话,我们在规定根以及遍历子节点的时候都加上随机化,那么基本是卡不掉了(喜欢快读快写也可以加)。
现在看实测,最长的点时间也不超过 1.5 s,而时限是 5 s,说明 COCI 出题人还有点良心 完全可以。
CODE
-
树状树组:(去掉了快读,不是上面那个)用时 2.45 s,内存 33.40 MB,单个测试点最长时间为 1.57 s。
#include<bits/stdc++.h> #define INF 0x3f3f3f3f #define ll long long #define tomax(a,b) ((a)=max((a),(b))) #define tomin(a,b) ((a)=min((a),(b))) #define RCL(a,b,c,d) memset((a),(b),sizeof(c)*(d)) #define FOR(i,a,b) for(register int i=(a);i<=(b);++i) #define DOR(i,a,b) for(register int i=(a);i>=(b);--i) #define EDGE(g,i,u,x) for(register int (i)=(g).h[(u)],(x)=(g).v[(i)];(i);(i)=(g).nxt[(i)],(x)=(g).v[(i)]) #define main Main();signed main(){ios::sync_with_stdio(0);cin.tie(0);return Main();}signed Main using namespace std; constexpr int N=1e5+10,lN=17,lV=lN+1; int n,Q,idx,Bl; int b[N],dl[N],dr[N],id[N<<1],pa[N],ans[N],dep[N],dfn[N<<1],siz[N],son[N]; int fa[N][lV],mi[N][lV]; ll sum; struct edge{ int u,v,w,c,s; void Scan(){ cin>>u>>v>>w>>c>>s; } }e[N]; struct Query{ int l,r,idx;ll w; friend bool operator <(Query a,Query b){ return id[a.l]^id[b.l]?a.l<b.l:(id[a.l]&1)^(a.r>b.r); } }qr[N]; struct BIT{ #define lowbit(a) ((a)&-(a)) ll c[N]; void Update(int x,int v){ if(x<=0)return; for(int i=x;i<=b[0];i+=lowbit(i))c[i]+=v; } int Bound(ll x){ ll sum=0;int ans=0; DOR(i,lN,0)if(ans+(1<<i)<=b[0]&&sum+c[ans+(1<<i)]<x)sum+=c[ans+=1<<i]; return ans+1; } #undef lowbit }B; struct CFS{ int tot,h[N],v[N<<1],nxt[N<<1]; void att(int U,int V){ v[++tot]=V,nxt[tot]=h[U],h[U]=tot; } void con(int U,int V){ att(U,V),att(V,U); } }g; void Build(int u){ dep[u]=dep[fa[u][0]]+1,mi[u][0]=e[pa[u]].s,siz[u]=1; FOR(i,1,lN)fa[u][i]=fa[fa[u][i-1]][i-1],mi[u][i]=min(mi[u][i-1],mi[fa[u][i-1]][i-1]); EDGE(g,i,u,v)if(v^fa[u][0]) pa[v]=i+1>>1,fa[v][0]=u,Build(v),siz[u]+=siz[v],son[u]=(siz[son[u]]>siz[v]?son[u]:v); } void Divide(int u){ dfn[dl[u]=++idx]=u; if(son[u])Divide(son[u]); EDGE(g,i,u,v)if(son[u]!=v&&fa[u][0]!=v)Divide(v); dfn[dr[u]=++idx]=u; } int Lca(int u,int v){ if(dep[v]<dep[u])swap(u,v); DOR(i,lN,0)if(dep[v]-dep[u]&1<<i)v=fa[v][i]; if(u==v)return u; DOR(i,lN,0)if(fa[u][i]^fa[v][i])u=fa[u][i],v=fa[v][i]; return fa[v][0]; } int Query(int u,int pa){ int ans=INF; DOR(i,lN,0)if(dep[fa[u][i]]>=dep[pa])tomin(ans,mi[u][i]),u=fa[u][i]; return ans; } bool vis[N]; void Update(int x){ if(x>1)sum+=(vis[x]?-1:1)*e[pa[x]].c,B.Update(e[pa[x]].w,(vis[x]?-1:1)*e[pa[x]].c),vis[x]^=1; } signed main(){ cin>>n; FOR(i,1,n-1)e[i].Scan(),g.con(e[i].u,e[i].v),b[i]=e[i].w; sort(b+1,b+n),b[0]=unique(b+1,b+n)-b-1; FOR(i,1,n-1)e[i].w=lower_bound(b+1,b+b[0]+1,e[i].w)-b; cin>>Q,Build(1),Divide(1),Bl=ceil(2.0*n/sqrt(Q)); FOR(i,1,n<<1)id[i]=(i-1)/Bl+1; FOR(i,1,Q){ int u,v,pa;cin>>u>>v>>qr[i].w,pa=Lca(u,v); if(dl[u]>dl[v])swap(u,v); qr[i].l=pa==u?dl[u]+1:dr[u],qr[i].r=dl[v],ans[qr[i].idx=i]=min(Query(u,pa),Query(v,pa)); } sort(qr+1,qr+Q+1); int l=1,r=0; FOR(i,1,Q){ while(r<qr[i].r)Update(dfn[++r]); while(l>qr[i].l)Update(dfn[--l]); while(r>qr[i].r)Update(dfn[r--]); while(l<qr[i].l)Update(dfn[l++]); if(qr[i].w<sum)tomin(ans[qr[i].idx],b[B.Bound(qr[i].w+1)]); } FOR(i,1,Q)cout<<ans[i]<<endl; return 0; }
-
分块:(卑微的记录.)用时 1.15 s,内存 33.73 MB,单个测试点最长时间为 688 ms。
#include<bits/stdc++.h> #define INF 0x3f3f3f3f #define ll long long #define tomax(a,b) ((a)=max((a),(b))) #define tomin(a,b) ((a)=min((a),(b))) #define RCL(a,b,c,d) memset((a),(b),sizeof(c)*(d)) #define FOR(i,a,b) for(register int i=(a);i<=(b);++i) #define DOR(i,a,b) for(register int i=(a);i>=(b);--i) #define EDGE(g,i,u,x) for(register int (i)=(g).h[(u)],(x)=(g).v[(i)];(i);(i)=(g).nxt[(i)],(x)=(g).v[(i)]) #define main Main();signed main(){ios::sync_with_stdio(0);cin.tie(0);return Main();}signed Main using namespace std; constexpr int N=1e5+10,lN=17,lV=lN+1,sN=340+10; bool vis[N]; int n,Q,idx,Bl; int b[N],dl[N],dr[N],id[N<<1],pa[N],ans[N],dep[N],dfn[N<<1],siz[N],son[N]; int fa[N][lV],mi[N][lV]; ll sum; struct edge{ int u,v,w,c,s; void Scan(){ cin>>u>>v>>w>>c>>s; } }e[N]; struct Query{ int l,r,idx;ll w; friend bool operator <(Query a,Query b){ return id[a.l]^id[b.l]?a.l<b.l:(id[a.l]&1)^(a.r>b.r); } }qr[N]; struct Block{ int Bl,Bn; int st[sN],en[sN],id[N]; ll Sum[sN],sum[N]; void Init(int n){ Bl=sqrt(n),Bn=(n-1)/Bl+1; FOR(i,1,Bn){ st[i]=en[i-1]+1,en[i]=min(n,st[i]+Bl-1); FOR(j,st[i],en[i])id[j]=i; } } void Update(int x,int d){ sum[x]+=d,Sum[id[x]]+=d; } int Query(ll x){ int Idx=0,idx=0;ll s=0; for(Idx=0;Idx<=Bn;s+=Sum[Idx],++Idx)if(s+Sum[Idx]>x)break; for(idx=st[Idx];idx<=en[Idx];s+=sum[idx],++idx)if(s+sum[idx]>x)break; return idx; } }B; struct CFS{ int tot,h[N],v[N<<1],nxt[N<<1]; void att(int U,int V){ v[++tot]=V,nxt[tot]=h[U],h[U]=tot; } void con(int U,int V){ att(U,V),att(V,U); } }g; void Build(int u){ dep[u]=dep[fa[u][0]]+1,mi[u][0]=e[pa[u]].s,siz[u]=1; FOR(i,1,lN)fa[u][i]=fa[fa[u][i-1]][i-1],mi[u][i]=min(mi[u][i-1],mi[fa[u][i-1]][i-1]); EDGE(g,i,u,v)if(v^fa[u][0]) pa[v]=i+1>>1,fa[v][0]=u,Build(v),siz[u]+=siz[v],son[u]=(siz[son[u]]>siz[v]?son[u]:v); } void Divide(int u){ dfn[dl[u]=++idx]=u; if(son[u])Divide(son[u]); EDGE(g,i,u,v)if(son[u]!=v&&fa[u][0]!=v)Divide(v); dfn[dr[u]=++idx]=u; } int Lca(int u,int v){ if(dep[v]<dep[u])swap(u,v); DOR(i,lN,0)if(dep[v]-dep[u]&1<<i)v=fa[v][i]; if(u==v)return u; DOR(i,lN,0)if(fa[u][i]^fa[v][i])u=fa[u][i],v=fa[v][i]; return fa[v][0]; } int Query(int u,int pa){ int ans=INF; DOR(i,lN,0)if(dep[fa[u][i]]>=dep[pa])tomin(ans,mi[u][i]),u=fa[u][i]; return ans; } void Update(int x){ if(x>1)sum+=(vis[x]?-1:1)*e[pa[x]].c,B.Update(e[pa[x]].w,(vis[x]?-1:1)*e[pa[x]].c),vis[x]^=1; } signed main(){ cin>>n; FOR(i,1,n-1)e[i].Scan(),g.con(e[i].u,e[i].v),b[i]=e[i].w; sort(b+1,b+n),b[0]=unique(b+1,b+n)-b-1,B.Init(b[0]); FOR(i,1,n-1)e[i].w=lower_bound(b+1,b+b[0]+1,e[i].w)-b; cin>>Q,Build(1),Divide(1),Bl=ceil(2.0*n/sqrt(Q)); FOR(i,1,n<<1)id[i]=(i-1)/Bl+1; FOR(i,1,Q){ int u,v,pa;cin>>u>>v>>qr[i].w,pa=Lca(u,v); if(dl[u]>dl[v])swap(u,v); qr[i].l=pa==u?dl[u]+1:dr[u],qr[i].r=dl[v],ans[qr[i].idx=i]=min(Query(u,pa),Query(v,pa)); } sort(qr+1,qr+Q+1); int l=1,r=0; FOR(i,1,Q){ while(r<qr[i].r)Update(dfn[++r]); while(l>qr[i].l)Update(dfn[--l]); while(r>qr[i].r)Update(dfn[r--]); while(l<qr[i].l)Update(dfn[l++]); if(qr[i].w<sum)tomin(ans[qr[i].idx],b[B.Query(qr[i].w)]); } FOR(i,1,Q)cout<<ans[i]<<endl; return 0; }
小小吐槽一下
我考试的时候想到了树状数组的做法,但是倍增写错了,然后就只有 Subtask 2 的分数了……(欲哭无泪)
后记
链接是不是点不进去啊?哈哈哈。 (皮一下很开心)