「Wdoi-6」最澄澈的空与海 题解
题目简述
已知一个左右都有
题目分析
这是一道结论题,先说结论,当一个二分图有且仅有一种解时,必定有节点的入度为
如何证明?
因为只要入度为
- 有解时,解的数量一定大于
1 - 无解时...就是无解(
废话)
无解的情况很好说明,只要找到一个例子就行了,剩下的就是有解的情况。
因为有解,所以我们可以先建一个节点一一对应的二分图,然后在这张图上加边,试图将其变成所有节点入度为
我们只考虑一边的节点,因为两边是对称的。
左边的每个节点都已经有了一个右边的第一选择,也就是现在连着的节点,他们还需要一个右边的第二选择来让自己的度数变成
两个不同的节点可能选相同的节点作为第二选择,我们按第二选择的不同节点数量来分类讨论。
当作为第二选择的节点的数量为
当作为第二选择的节点的数量为
如下图所示,黄色的是新增的边,第二选择的
同样地,当作为第二选择的节点的数量为
但这只是当第二选择节点对面的所有节点的第二选择都不重复时的必然情况,如果重复了呢?
如下图所示,作为第二选择的
熟悉的交叉又出现了,对,就是第二选择节点数为
通过以上的观察(并没有严格证明,只是助于理解),我们可以得到一个重要结论:
- 当第二选择节点对面的节点的第二选择不重复时,直接就能找到第二种匹配方法
- 有重复时,会出现之前的某种情况,然后找到第二种匹配方法。
于是乎,不管怎样添边,都会出现第二种匹配方法,所以当二分图的所有节点度数为
结论有了,该怎么用呢?需要注意的是这只是一个必要非充分条件,也就是说满足题意所说的有且仅有一个解的情况一定会出现入度为
幸运的是,咱们会写程序。我们可以每次找到入度为
既然它俩的关系已经确定了,那么它俩就对剩余节点的匹配就没有影响了,我们就可以将它俩连点带边全部删掉。注意,边也要删掉,以此减少某些节点的入度。
就这样一直迭代下去,如果可以全部删完,那么就是满足题意的,删不完就是说有入度都大于
每次寻找入度为
code:
#include<bits/stdc++.h>
#define N 2000005
#define ll long long
using namespace std;
int t,n,m,cnt;
vector<int> to[N];
int in[N];
bool del[N];
void solve(){
cin>>n>>m;
for(int i=1,u,v;i<=m;i++){
cin>>u>>v; v+=n; //右边的点重新编号
to[u].push_back(v); in[v]++;
to[v].push_back(u); in[u]++;
}
queue<int> q; cnt=0;
for(int i=1;i<=2*n;i++)
if(in[i]==1)q.push(i); //将入度为1的点入队
while(!q.empty()){
int now=q.front(),buf=0; q.pop();
if(del[now]||in[now]!=1)continue; //如果已经没删掉了,或者入度不为1了,那就跳过。已经入队的点的状态也可能被改变,因为这里是左右两边一起迭代的。
del[now]=true; cnt++; //删掉这个点,删掉的数量加1
while(del[to[now][buf]])buf++; //找到仅存的那一条边
del[to[now][buf]]=true; cnt++; //顺着仅存的边找到另一个点,删掉
for(int i=0;i<to[to[now][buf]].size();i++) //删边,没有真的删,只是减少目标节点的入度,这也是为什么要用while找仅存的边。
if(!del[to[to[now][buf]][i]]&&(--in[to[to[now][buf]][i]])==1)
q.push(to[to[now][buf]][i]); //如果还没被删,并且入度减少为1了就入队
}
if(cnt==n*2)cout<<"Renko"<<endl; //全删了
else cout<<"Merry"<<endl; //没全删
for(int i=1;i<=n*2;i++)in[i]=del[i]=0,to[i].clear(); //多组数据,要初始化
}
int main(){
cin>>t;
while(t--)solve();
return 0;
}