[Codeforces 1027 F] Session in BSU [并查集维护二分图匹配问题]

题面

传送门

思路

真是一道神奇的题目呢

题目本身可以转化为二分图匹配问题,要求右半部分选择的点的最大编号最小的一组完美匹配

注意到这里左边半部分有一个性质:每个点恰好连出两条边到右半部分

那么我们可以利用这个性质

考虑一个左边的点和它右边联通的两个点,发现这两个点只能选择一个和这个左边的点匹配

那么我们考虑把这个点点匹配的模型转化成点边匹配

我们在同一个左边点连的两个右边点之间连边,那么问题就变成了一个点和一条相邻的边匹配,求完美匹配的问题了

而这个问题,我们显然可以用并查集来很好的解决

考虑一个联通块,如果联通块中边数大于点数,那么可以直接输出-1,因为此时边代表的是原题中需要满足的那个东西(考试),而边比点多就一定无解了

如果边数等于点数,那么显然这个联通块中做出最大值贡献的点应该是编号最大的那个。这种情况下我们会得到一棵基环树,每个边恰好有一个点和它匹配

如果边数小于点数,那么这个联通块肯定是一个树,那么此时对答案的贡献就是联通块中的次大值了,因为可以以最大值为根,不选根,剩下的每个点匹配连接它和它父亲的那条边

用并查集维护联通块内部的点数、边数、最大值和次大值,最后扫一遍,取所有联通块的最大贡献的最大值即可

Code

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<cassert>
#define ll long long
using namespace std;
inline int read(){
    int re=0,flag=1;char ch=getchar();
    while(ch>'9'||ch<'0'){
        if(ch=='-') flag=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9') re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
    return re*flag;
}
int f[2000010],maxn[2000010],sec[2000010],cnte[2000010],cntn[2000010];
inline int find(int x){return ((f[x]==x)?x:f[x]=find(f[x]));}
inline void join(int x,int y){
    x=find(x);y=find(y);
    if(x==y){
        cnte[x]++;return;//注意这里也需要加一条边
    }
    f[x]=y;cnte[y]+=cnte[x]+1;cntn[y]+=cntn[x];
    int tmp[4];
    tmp[0]=maxn[x];tmp[1]=sec[x];tmp[2]=maxn[y];tmp[3]=sec[y];
    sort(tmp,tmp+4);
    maxn[y]=tmp[3];sec[y]=tmp[2];
}
struct node{
    int val,num,pos;
}a[2000010];
int n,cnt;
inline bool cmp1(node l,node r){
    return l.val<r.val;
}
inline bool cmp2(node l,node r){
    return l.num<r.num;
}
int main(){
    n=read();int i,j;
    for(i=1;i<=n;i++) a[i].val=read(),a[i+n].val=read(),a[i].num=i,a[i+n].num=i+n;
    sort(a+1,a+(n<<1)+1,cmp1);
    for(i=1;i<=(n<<1);i++){
        j=i;
        while(a[i+1].val==a[i].val&&i<(n<<1)) i++;
        cnt++;f[cnt]=cnt;cnte[cnt]=0;cntn[cnt]=1;maxn[cnt]=a[i].val;sec[cnt]=0;
        while(j<=i) a[j].pos=cnt,j++;
    }
    sort(a+1,a+(n<<1)+1,cmp2);
    for(i=1;i<=n;i++){
        join(a[i].pos,a[i+n].pos);
    }
    int ans=0;
    for(i=1;i<=cnt;i++){
        if(f[i]==i){
            if(cnte[i]>cntn[i]){
                puts("-1");return 0;
            }
            if(cnte[i]==cntn[i]) ans=max(ans,maxn[i]);
            if(cnte[i]<cntn[i]) ans=max(ans,sec[i]);
        }
    }
    cout<<ans<<'\n';
}

原文地址:https://www.cnblogs.com/dedicatus545/p/9501293.html

时间: 2024-08-05 10:53:17

[Codeforces 1027 F] Session in BSU [并查集维护二分图匹配问题]的相关文章

CF 1027F Session in BSU (并查集+树上构造)

题目大意:你可以在第$ai$天或者第$bi$天进行第$i$场考试,每天最多进行一场考试,求把所有考试都考完的最早结束时间 由于天数可能很大,需要离散 把问题抽象成一棵树,每个点最多被"分配"一条边,现在要删点 画画图可以发现 如果一个联通块是一棵树,那么可以删去至多一个点 如果一个联通块是一个单环树(n个点n条边),那么一个点都不能删掉 如果一个联通块边数大于点数,会发现无法把每个点只分配一条边,不合法,输出-1 判树还是单环树,求一个联通块内点的度总和/2和点数比较即可 并查集维护一

线段树、最短路径、最小生成树、并查集、二分图匹配、最近公共祖先--C++模板

线段树(区间修改,区间和): #include <cstdio> #include <iostream> #include <cstring> using namespace std; int c[1000000],n,m; char s; void update(int p,int l,int r,int x,int add) { int m=(l+r) / 2; if (l==r) { c[p]+=add; return; } if (x<=m) update

HD2444The Accomodation of Students(并查集判断二分图+匹配)

The Accomodation of Students Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 4091    Accepted Submission(s): 1876 Problem Description There are a group of students. Some of them may know each ot

CodeForces 776D The Door Problem【并查集】

CodeForces 776D The Door Problem[并查集]并查集 设 f 1--m 表示 开的情况 m+1--2*m 表示关的情况 对于每盏灯 如果他 是关的 则 x--y x+m--y+m 表示要同关 或者同开 如果他 是开的 则 x+m--y x--y+m 表示一个关 一个开如果一盏灯 的 x 连向 了 x+m 则表示是矛盾了 那么久是错误的 题意:给你n个门,和m组开关,每扇门都有两个开关控制,每个开关控制x扇门,如果选择了某组开关,则使这组开关里的每个开关控制的所有的门按

Codeforces Round #250 (Div. 1) B 并查集

坑!神坑!深坑!,WA了几十把,最终答案  (ans * 2)/(n * 1.0 * (n - 1)) 要是写成(ans * 2)/(n *(n - 1)*1.0)就是WA,不明白为啥,愤怒的我 全改成double就可以了,若前面变量用了int的 答案必须是前一种写法, 题目不是特别难,没啥思路画一画就有思路了,10^5的n去扫肯定是要超时的,那就想想一次性的10^5,发想通过m是可以的,建边,边权就是两端点中小的那个,然后对最终答案的种数进行分析,发现其实就是 每次你要连接的两块连通块的个数相

Codeforces.1051G.Distinctification(线段树合并 并查集)

题目链接 \(Description\) 给定\(n\)个数对\(A_i,B_i\).你可以进行任意次以下两种操作: 选择一个位置\(i\),令\(A_i=A_i+1\),花费\(B_i\).必须存在一个位置\(j\),满足\(A_i=A_j,\ i\neq j\),才可以进行. 选择一个位置\(i\),令\(A_i=A_i-1\),花费\(-B_i\).必须存在一个位置\(j\),满足\(A_i=A_j+1\),才可以进行. 你需要对于所有\(i\in[1,n]\),求使得\(A_1,A_2,

[USACO18JAN] MooTube (并查集 -&gt; 维护连通性)

题目大意:给你一棵边权树,定义两点间距离为它们唯一路径上的最小路权,求与某点距离不大于K(k为已知)的点的数量 带权并查集维护集合内元素总数 路和问题 都按权值大到小排序,枚举问题, 建权值不小于K的边,并查集维护连通性,求集合元素内总数即可 1 #include <bits/stdc++.h> 2 #define N 200100 3 #define inf 0x3f3f3f3f 4 using namespace std; 5 6 int n,q,cnt; 7 int fa[N],f[N]

Codeforces Round #541 (Div. 2) D 并查集 + 拓扑排序

https://codeforces.com/contest/1131/problem/D 题意 给你一个n*m二维偏序表,代表x[i]和y[j]的大小关系,根据表构造大小分别为n,m的x[],y[],使得两个数组中最大的数尽量小 题解 按照偏序表,构造出从小到大的拓扑图 如何解决相等的数的偏序关系? 用并查集缩点后再进行拓扑排序 如何解决最大的数最小? 只需要使得同一层的数相同就行,可以一批处理栈中的元素,对于一批栈中的元素产生的新点,先放进一个容器里,然后等到这批栈清空了,再把这个容器中的点

CodeForces 593D Happy Tree Party [LCA+并查集]

题意:给一棵树,每条边有一个权值,给两种操作,第一种是询问y向下整除从a到b的最短路径中每条边的权值后y的值,第二种是改变某条边的权值. 思路:y的最大值为1e18,最多除大于等于2的数不超过60次即可将y变为0,先dfs以任意一点为根建树,记录每个点的深度和它的父结点并将边权转化为点权, 再搞个并查集,将权值为1的点压缩,即使pre[u]=g[u];(u变成u的爸爸). 1 #include<bits/stdc++.h> 2 #define fi first 3 #define se sec