CF1967D Long Way to be Non-decreasing 题解
知识点
二分答案,基环树。
题意分析
给定一个包含 \(n\) 个元素的数组 \(\{ a_i \}\) 和一个 \(m\) 个元素的数组 \(\{ b_i \}\)。
定义每次操作为:在 \(\{ a_i \}\) 中选择任意个数,假设某个选的数为 \(a_i\),那么将其变为 \(b_{a_i}\)。
问使 \(\{ a_i \}\) 变为单调不降的最少操作次数。
思路分析
看到“最少”这个词语就应该考虑二分答案,再看性质,它明显满足单调性,那就是二分答案无疑了。
那么考虑二分的检验过程:
设现在二分的次数为 \(x\),一开始的 \(mx=m\)。
我们可以从最后一个开始检验,如果不能到 \(mx\) 或到 \(mx\) 的距离大于 \(x\),就将 \(mx\) 减一,再继续重复,做一个尺取操作,如果 \(mx\) 有降到 \(0\) 及以下,就说明不合法。
我们现在再考虑如何求距离。
明显,如果我们建图,生成 \(m\) 个点,再往第 \(i\) 个点上连一条向 \(b_i\) 的边,那么就生成了一个内向基环森林,在这里,有两种求距离的方式:
- 把环上的每一个点都作为一棵子树的根,将距离分为环上距离与树上距离;
- 把环上某个点连向父节点的边砍掉,重新建树,就可以转换为新树上的距离。
至此,思路已经结束。
CODE
下面分别给出两种基环树(森林)上求距离的方法代码。
-
把环上的每一个点都作为一棵子树的根,将距离分为环上距离与树上距离;
#include<bits/stdc++.h> #define INF 0x3f3f3f3f #define ll long long #define max(a,b) ((a)<(b)?(b):(a)) #define min(a,b) ((a)>(b)?(b):(a)) #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=1e6+10; int T,n,m,idx; int a[N],b[N],dl[N],dr[N],dep[N],head[N]; struct CFS{ int tot,h[N],v[N],nxt[N]; void Clear(int n){ tot=0,RCL(h,0,int,n+5); } void att(int U,int V){ v[++tot]=V,nxt[tot]=h[U],h[U]=tot; } }g; struct DSU{ int fa[N],siz[N]; int operator [](int i){ return get(i); } void Init(int n){ FOR(i,1,n)fa[i]=i,siz[i]=1; } int get(int x){ return fa[x]^x?fa[x]=get(fa[x]):x; } bool Check(int u,int v){ return get(u)==get(v); } bool Merge(int u,int v){ int x=get(u),y=get(v); if(x==y)return 0; if(siz[x]>siz[y])swap(x,y); return fa[y]=x,siz[x]+=siz[y],head[x]|=head[y],1; } }D; void Build(int u){ dl[u]=++idx; EDGE(g,i,u,v)dep[v]=dep[u]+1,Build(v); dr[u]=idx; } bool Rela(int u,int v){ return dl[v]<=dl[u]&&dl[u]<=dr[v]; } int Dis(int u,int v){ if(!D.Check(u,v))return INF; if(Rela(u,v))return dep[u]-dep[v]; return Rela(b[head[D[u]]],v)?dep[u]+1+(dep[b[head[D[u]]]]-dep[v]):INF; } bool Check(int x){ int tim=m; DOR(i,n,1){ while(tim&&Dis(a[i],tim)>x)--tim; if(!tim)return 0; }return 1; } signed Cmain(){ cin>>n>>m,idx=0,D.Init(m),g.Clear(m),RCL(head,0,int,m+5); FOR(i,1,n)cin>>a[i]; FOR(i,1,m)cin>>b[i]; FOR(i,1,m){ if(D.Merge(i,b[i]))g.att(b[i],i); else head[D[i]]=i; } FOR(i,1,m)if(D[i]==i)dep[head[i]]=0,Build(head[i]); int ans=-1; for(int l=0,r=m,mid=l+r>>1;l<=r;mid=l+r>>1)Check(mid)?ans=mid,r=mid-1:l=mid+1; cout<<ans<<endl; return 0; } signed main(){ for(cin>>T;T;--T)Cmain(); return 0; }
-
把环上某个点连向父节点的边砍掉,重新建树,就可以转换为新树上的距离。
#include<bits/stdc++.h> #define INF 0x3f3f3f3f #define ll long long #define max(a,b) ((a)<(b)?(b):(a)) #define min(a,b) ((a)>(b)?(b):(a)) #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=1e6+10; int T,n,m,idx,cnt; int a[N],b[N],d[N],dl[N],dr[N],id[N],num[N],rt[N],siz[N],dep[N],head[N]; queue<int> q; struct CFS{ int tot,h[N],v[N],nxt[N]; void Clear(int n){ tot=0,RCL(h,0,int,n+5); } void att(int U,int V){ v[++tot]=V,nxt[tot]=h[U],h[U]=tot; } }g; struct DSU{ int fa[N],siz[N]; int operator [](int i){ return get(i); } void Init(int n){ FOR(i,1,n)fa[i]=i,siz[i]=1,head[i]=0; } int get(int x){ return fa[x]^x?fa[x]=get(fa[x]):x; } bool Check(int u,int v){ return get(u)==get(v); } bool Merge(int u,int v){ int x=get(u),y=get(v); if(x==y)return 0; if(siz[x]>siz[y])swap(x,y); return fa[y]=x,siz[x]+=siz[y],head[x]|=head[y],1; } }D; void Build(int u){ dl[u]=++idx; EDGE(g,i,u,v)if(!d[v])rt[v]=rt[u],dep[v]=dep[u]+1,Build(v); dr[u]=idx; } void Circle(int u){ id[u]=cnt,++siz[cnt]; if(!id[b[u]])num[b[u]]=num[u]+1,Circle(b[u]); } bool Rela(int u,int v){ return dl[v]<=dl[u]&&dl[u]<=dr[v]; } int Dis(int u,int v){ if(!D.Check(u,v))return INF; if(Rela(u,v))return dep[u]-dep[v]; return rt[v]!=v?INF:num[v]-num[rt[u]]+(num[v]>=num[rt[u]]?0:siz[id[rt[u]]])+dep[u]-dep[rt[u]]; } bool Check(int x){ int tim=m; DOR(i,n,1){ while(tim&&Dis(a[i],tim)>x)--tim; if(!tim)return 0; }return 1; } signed Cmain(){ cin>>n>>m,idx=0,cnt=0,D.Init(m),g.Clear(m),RCL(d,0,int,m+5),RCL(id,0,int,m+5); FOR(i,1,n)cin>>a[i]; FOR(i,1,m)cin>>b[i],++d[b[i]]; FOR(i,1,m){ g.att(b[i],i); if(!D.Merge(i,b[i]))head[D[i]]=i; } FOR(i,1,m)if(!d[i])q.push(i); while(!q.empty()){ int u=q.front();q.pop(); if(!(--d[b[u]]))q.push(b[u]); } FOR(i,1,m)if(d[i])rt[i]=i,Build(i); FOR(i,1,m)if(D[i]==i)dep[head[i]]=0,Build(head[i]); FOR(i,1,m)if(D[i]==i)siz[++cnt]=0,num[head[i]]=1,Circle(head[i]); int ans=-1; for(int l=0,r=m,mid=l+r>>1;l<=r;mid=l+r>>1)Check(mid)?ans=mid,r=mid-1:l=mid+1; cout<<ans<<endl; return 0; } signed main(){ for(cin>>T;T;--T)Cmain(); return 0; }
总结
这题最难的部分在于二分答案的检验过程与基环森林的构建,攻克后就十分简单,十分适合练手。
标签:Non,return,get,int,题解,head,Long,siz,define From: https://www.cnblogs.com/Cat-litter/p/18188152