CF1967D Long Way to be Non-decreasing 题解

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\) 的边,那么就生成了一个内向基环森林,在这里,有两种求距离的方式:

  1. 把环上的每一个点都作为一棵子树的根,将距离分为环上距离与树上距离;
  2. 把环上某个点连向父节点的边砍掉,重新建树,就可以转换为新树上的距离。

至此,思路已经结束。


CODE

下面分别给出两种基环树(森林)上求距离的方法代码。

  1. 把环上的每一个点都作为一棵子树的根,将距离分为环上距离与树上距离;

    #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;
    }
    
  2. 把环上某个点连向父节点的边砍掉,重新建树,就可以转换为新树上的距离。

    #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;
    }
    

总结

这题最难的部分在于二分答案的检验过程与基环森林的构建,攻克后就十分简单,十分适合练手。

posted @ 2024-05-12 20:41  Add_Catalyst  阅读(41)  评论(0)    收藏  举报