青蛙过河(二分+并查集)

Posted 曼切斯特的流氓

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了青蛙过河(二分+并查集)相关的知识,希望对你有一定的参考价值。

青蛙过河

来回x次,也就是从起点一共2x个青蛙走一遍到终点

用一个数组记录每次送多少个青蛙由i–>j

具体看acwing解析

#include <bits/stdc++.h>
#define ll long long

using namespace std;
typedef pair<int,int>PII;

const int N=1e5+10;
int n,m,L;
ll s[N];
int a[N],p[N],h[N];

int findFa(int x) 
	return x==p[x]?x:p[x]=findFa(p[x]);


int check(int mid) 

	for(int i=1; i<=n-1; i++)a[i]=h[i],p[i]=i;

	for(int i=1; i<=n; i++)s[i]=0;
	s[0]=m;//起点有2*x只青蛙
	for(int i=0; i<n; i++) 

		int j=i+mid;

		if(j>=n)  //一步直接到对岸
			s[n]+=s[i];
			s[i]=0;
		 else 
			while(1) 
				j=findFa(j);
				if(s[i]==0||j<=i)break;//当前i号位置不存在青蛙或者i到i+mid内没有石墩可以跳了
				//将x个青蛙由i运送到j
				ll x=min((ll)a[j],s[i]);
				s[j]+=x;
				a[j]-=x,s[i]-=x;
				//j位置石墩用完了,就向前找新的石墩
				if(a[j]==0)p[j]=findFa(j-1);
			

		
	
	return s[n]==m;



int main() 

	cin>>n>>m;
	m*=2;

	for(int i=1; i<=n-1; i++) cin>>h[i];

	int l=1,r=n;
	//如果满足check应该找最大距离越小越好
	//如果不满足证明这个距离太小了应该放大 
	while(l<r) 
		int mid=l+r>>1;
		if(check(mid))r=mid;
		else l=mid+1;
	

	cout<<r<<endl;






	return 0;

P1525 关押罪犯 - 二分+二分图染色||并查集

传送门

一.二分+二分图染色

最小的最大,考虑二分。

每次二分影响值,将影响值不小于mid的两个人关进不同的监狱,即染成不同的颜色,分到二分图的两个不同的集合中。若最终形成的图是二分图,说明这种分法可行,影响值还有变小的可能;否则影响值只能更大。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=20000+100,M=100000+100;
int n,m;
struct node{
    int to,nxt;
    long long w;
}e[M<<1];
int head[N],tot;
void add(int u,int v,long long w){
    e[++tot]=(node){v,head[u],w};
    head[u]=tot;
}
struct ed{
    int u,v;
    long long w;
}E[M];
int col[N];
bool dfs(int u,int c,long long mid){
    col[u]=c;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(e[i].w>=mid){
            if(col[v]==col[u]) return false;
            if(!col[v]&&!dfs(v,3-c,mid)) return false;    
        }
    }
    return true;
}
bool check(long long mid){
    for(int i=1;i<=n;i++) col[i]=0;
    //不必重新建图,只保留边权>mid的即可 
    for(int i=1;i<=n;i++) if(!col[i]) if(!dfs(i,1,mid)) return false;
    return true;
}
int main(){
    scanf("%d%d",&n,&m);
    long long l=0,r=-(1<<30);
    for(int i=1;i<=m;i++){
        int u,v;
        long long w;
        scanf("%d%d%lld",&u,&v,&w);
        int c=u;
        u=min(u,v);v=max(c,v);
        add(u,v,w);add(v,u,w);
        E[i]=(ed){u,v,w};
        r=max(r,w);
    }
    r++;//扩大边界,防止漏解 
    while(l+1<r){
        long long mid=(l+r)>>1;
        if(check(mid)) r=mid;
        else l=mid;
    }
    printf("%lld",l);
    return 0;
}

二.并查集

贪心地想,将每对罪犯按影响值从大到小进行排序,每次将两个罪犯分到两个监狱中,直到出现冲突的情况,输出答案。

我们可以通过并查集来维护两个集合并查询集合所属关系。对于每个罪犯i,集合fa[i]表示在监狱1,集合fa[i+n]表示在监狱2。若出现两个罪犯在同一监狱的情况,说明出现了冲突,此时这对罪犯的影响值就是最小的影响值即为答案。最后注意特判没有冲突的情况。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=20000+100,M=100000+100;
struct node{
    int u,v;
    long long w;
}e[M];
bool cmp(node a,node b){
    return a.w>b.w;
}
int fa[N<<1];
int find(int x){
    return fa[x]==x?x:fa[x]=find(fa[x]);
}
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int u,v;
        long long w;
        scanf("%d%d%lld",&u,&v,&w);
        e[i]=(node){u,v,w};
    }
    sort(e+1,e+m+1,cmp);
    for(int i=1;i<=(n<<1);i++) fa[i]=i;
    for(int i=1;i<=m;i++){
        int u=e[i].u,v=e[i].v;
        int fu=find(u),fv=find(v);
        if(fu==fv){
            printf("%lld",e[i].w);
            return 0;
        }
        else {
            fa[fu]=find(v+n);
            fa[fv]=find(u+n);
        }
    }
    printf("0");
    return 0;
}

 

以上是关于青蛙过河(二分+并查集)的主要内容,如果未能解决你的问题,请参考以下文章

CF1253F Cheap Robot(神奇思路,图论,最短路,最小生成树/Kruskal 重构树/并查集)

Nyo j布线问题(并查集&&图论)

hiho14 无间道之并查集图论--并查集

Union-Find 并查集算法

HDU 5652 图论之并查集

并查集 - 图论基础