1.P1525 [NOIP2010 提高组] 关押罪犯
原题指路:P1525 [NOIP2010 提高组] 关押罪犯 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P1525
蒟蒻用的并查集。
1.排序,尽可能让怒气值大的两个罪犯分到不同的监狱中。
2.归并并查集:
(1)“敌人的的敌人就是朋友”,若x和y有仇,合并x与y的敌人在同一所监狱内。
(2)若两个罪犯只能分配到同一个监狱里时,已经为排序后的最小值,直接输出。
(3)最后,若所有有仇的罪犯都被分配到了不同的监狱内,输出0。
#include <bits/stdc++.h>
using namespace std;
int n,m;
int a[20005],b[20005];//a[]为所有的情况,b[]标记某个罪犯是否有敌人,并用于合并
struct chongtu
{
int x,y,z;
};
chongtu f[100005];
bool cmp(chongtu a,chongtu b)//从大到小排序
{
return a.z>b.z;
}
int find_(int x)//查找祖先节点
{
if(a[x] == x) return x;
else return a[x] = find_(a[x]);
}
void conbine(int x,int y)//合并
{
x = find_(a[x]);
y = find_(a[y]);
a[x] = y;
}
bool check(int x,int y)//查找是否有相同的祖先节点
{
x = find_(x);
y = find_(y);
if(x == y) return true;
return false;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
a[i] = i;//初始化父节点为自己
for(int i=1;i<=m;i++)
cin>>f[i].x>>f[i].y>>f[i].z;
sort(f+1,f+m+1,cmp);
for(int i=1;i<=m+1;i++){//循环到m+1时,所有有仇的罪犯都被分配到了不同的监狱内,且f[m+1].z=0,所以i=m+1时,输出0
if(check(f[i].x,f[i].y)){
cout<<f[i].z;
break;
}//如果两个罪犯已经在同一所监狱里,则为排序后的最小值,直接输出
else{
if(!b[f[i].x])
b[f[i].x] = f[i].y;//标记敌人
else
conbine(b[f[i].x],f[i].y);//敌人的敌人合并
if(!b[f[i].y])
b[f[i].y] = f[i].x;
else
conbine(b[f[i].y],f[i].x);
}
}
return 0;
}
2.P3386 【模板】二分图最大匹配
匈牙利算法:对左边的n个点进行n次dfs,如果能在右边找到与之对应的点,答案++。
#include <bits/stdc++.h>
using namespace std;
const int MAXN=510;
int n,m,e;
int g[MAXN][MAXN];
int link[MAXN];
bool used[MAXN];
bool dfs(int u)
{
for(int v=1;v<=m;v++){
if(g[u][v] && !used[v]){//右边 && 本次dfs没来过右边的v点
used[v] = true;
if(link[v]==-1 || dfs(link[v])){//右边的v点没配对 || 之前与v点配对的u点能找到其他与之配对的点
link[v] = u;
return true;
}
}
}
return false;//没找到
}
int hungary()
{
int res = 0;
memset(link,-1,sizeof(link));//右边点的配对情况
for(int u=1;u<=n;u++){
memset(used,false,sizeof(used));//每次dfs前都要初始化used[],表示右边的点都没来过
if(dfs(u))
res++;
}
return res;
}
int main()
{
cin>>n>>m>>e;
memset(g,0,sizeof(g));
for(int i=1;i<=e;i++){
int u,v;
cin>>u>>v;
g[u][v] = 1;
}
int ans = hungary();
cout<<ans;
return 0;
}
3.P1129 [ZJOI2007] 矩阵游戏
原题指路:P1129 [ZJOI2007] 矩阵游戏 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P1129
二分图的最大匹配——匈牙利算法
翻译一下:要使得方阵的主对角线(左上角到右下角的连线)上的格子均为黑色,而行列变换操作并不会改变方阵中1的个数,所以至少每一行每一列都要有一个1,才能化成主对角线上全为1。
构建二分图:行数为左边列,每一行中值为1的列数为右边列。
eg:
1 0 1 1 — 1,1 — 3
0 1 0 2 — 2
1 0 0 3 — 1
即二分图:
如果最大匹配数 == n,即每一行每一列都至少有一个1,输出Yes,否则输出No。
#include <bits/stdc++.h>
using namespace std;
int t,n;
int Map[205][205];//邻接矩阵
int link[205];//存右点对象
bool used[205];//右点对象是否访问过
bool dfs(int j)
{
for(int k=1;k<=n;k++){
if(Map[j][k] && !used[k]){//右边 && 在这一趟dfs中没有被访问
used[k] = true;
if(link[k]==-1 || dfs(link[k])){//右边的列数尚未配对 || 已经配对的列数能找到其他的行数与其配对
link[k] = j;
return true;
}
}
}
return false;//没找到记得返回
}
int hungary()
{
int res=0;
memset(link,-1,sizeof(link));//初始化:每个列数都没有行数与其配对
for(int j=1;j<=n;j++){
memset(used,false,sizeof(used));//每一趟dfs前,所有的列数都没有访问过,防止单次dfs重复搜
if(dfs(j)) res++;
}
return res;
}
int main()
{
cin>>t;
for(int i=1;i<=t;i++){
cin>>n;
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++){
cin>>Map[j][k];
}
//cout<<hungary();
//如果最大匹配数 == n,即每一行每一列都至少有一个1,就能化成主对角线均为1
if(hungary() == n) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
return 0;
}