目录
1,无线网络
题目链接:https://www.acwing.com/problem/content/description/4269/
这题很简单,就是一个朴素的并查集,每次进行打开电脑的操作时,我们遍历每一台已开机的电脑,如果有跟他距离小于给定条件的,就将他们加到一个集合中去 ,对于查询两个电脑是否能实现通信,就判断这两台电脑是否在一个集合中
代码如下:
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=1010;
int n,d;
int x[N],y[N],f[N],v[N];
char ch;
int p,q;
double get_dis(int a,int b)//求a和b的距离
{
double dx=x[a]-x[b],dy=y[a]-y[b];
return sqrt(dx*dx+dy*dy);
}
int find(int x)//并查集找到集合操作,并将路径上的点进行路径压缩,将集合中的点都指向根节点
{
if(x!=f[x])f[x]=find(f[x]);
return f[x];
}
int main()
{
scanf("%d%d",&n,&d);
for(int i=1;i<=n;i++)f[i]=i;//并查集初始化
for(int i=1;i<=n;i++)
scanf("%d%d",&x[i],&y[i]);
while(cin>>ch)
{
if(ch=='O')
{
scanf("%d",&p);
v[p]=1;//已经开机的电脑要标记一下
for(int i=1;i<=n;i++)//遍历所有已经开机的电脑,如果有符合条件的就加入到一个集合中
if(v[i]&&get_dis(p,i)<=d)
f[find(i)]=find(p);
}
else
{
scanf("%d%d",&p,&q);//查询两台电脑是否在一个集合中
int fp=find(p),fq=find(q);
if(fp==fq)puts("SUCCESS");
else puts("FAIL");
}
}
return 0;
}
2,可疑人员
题目链接:https://www.acwing.com/problem/content/4270/
对于每个社团,我们先找到第一个人 所在的集合,将社团中其他人所在的集合都与第一个人所在的集合合并,最终遍历所有人,就可以找到与0号学生在一个集合中的人
代码如下:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=30010;
int n,m,k;
int f[N];
int find(int x)
{
if(x!=f[x])f[x]=find(f[x]);
return f[x];
}
int main()
{
while(scanf("%d%d",&n,&m),n||m)//多组测试数据
{
for(int i=0;i<=n;i++)f[i]=i;//并查集初始化
while(m--)//m行
{
int x;
scanf("%d%d",&k,&x);
int fx=find(x);//先找到第一个点所在得集合
k--;
while(k--)
{
scanf("%d",&x);
f[find(x)]=fx;//将一个社团中后面每个点都与第一个点合并到一个集合中
}
}
int res=0;
for(int i=0;i<n;i++)res+=(find(i)==find(0));//找到跟0号点在一个集合中得所有人
printf("%d\n",res);
}
return 0;
}
3,多少张桌子
题目链接:https://www.acwing.com/problem/content/4288/
一个并查集的模板题,只需要将给定的关系合并到一个集合中去,然后判断一下有多少个集合,就知道需要多少张桌子了
代码如下:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;
int f[N];
int find(int x)//并查集找到集合操作,并将集合中的所有点进行路径压缩
{
if(x!=f[x])f[x]=find(f[x]);
return f[x];
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)f[i]=i;
while(m--)
{
int a,b;
scanf("%d%d",&a,&b);
int fa=find(a),fb=find(b);//查询a和b所在的区间
if(fa!=fb)//如果不在一个区间中就合并区间
f[fa]=fb;
}
int ans=0;
for(int i=1;i<=n;i++)
if(f[i]==i)//如果集合的根节点是自己,说明要用一个新桌子
ans++;
printf("%d\n",ans);
}
return 0;
}
4,多少个答案是错误的
题目链接:https://www.acwing.com/problem/content/description/4289/
这题给出的条件是两个点之间的区间和,那么我们就知道了两个点的关系,给了一个区间和,我们可以看成是用前缀和求区间和的操作,例如给定l,r的区间和为s,等价于从1~r的区间和减去1~l-1的区间和的答案为s,因为我们可以用带权并查集维护每个点到根节点的距离,这里可以理解为到1号点的距离,如果两个点的关系已知,就可以加入到一个集合中,对于已经在一个集合中的点,我们就可以知道这两个点的关系,从而判断是否会与描述冲突
还有一个区间合并时如何维护权值,同样的,假设给定a,b这段区间的和为s,那么等于告诉了我们a-1和b这两个点的关系,合并是如果将a-1所在集合的根节点fa指向b所在集合的根节点,那么点fa到fb的距离可以通过a-1和b两个点的关系求得,即为d[a-1]+d[fa]+s=d[b],可以得到s=d[b]-d[a-1]-s.
代码如下:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=2e5+10;
int n,m;
int f[N],d[N];//d[i]表示点i到其父节点的距离
int ans;
int find(int x)//找到x所在的集合,并更新该集合中所有点的权值
{
if(x!=f[x])
{
int root=find(f[x]);
d[x]+=d[f[x]];//更新集合中点的权值
f[x]=root;//路径压缩,这一步一定要在上一步下面,否则修改权值会出错
}
return f[x];
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)f[i]=i;
while(m--)
{
int a,b,x;
scanf("%d%d%d",&a,&b,&x);
a--;//前缀和的思想,a~b的和为等于x,等价于sum(1~b)-sum(1~a-1)=x
int fa=find(a),fb=find(b);
if(fa!=fb)//如果不在一个集合中,就将两个点合并到一个集合
{
f[fa]=fb;//将a的根节点指向b的根节点
d[fa]=d[b]-x-d[a];//更新a的根节点到b的根节点的权值
}
else//如果已经在一个集合中,就可以通过两个点的权值判断该描述是否会与前面的描述冲突
{
if(d[b]-d[a]!=x)//如果权值相减不等于x,说明有冲突
ans++;
}
}
printf("%d\n",ans);
return 0;
}
5,食物链
题目链接:https://www.acwing.com/problem/content/242/
这题在算法提高课中解释的很清楚了,用带权并查集维护有三种集合具体可以查找 高级数据结构(算法提高课)
代码如下:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=50010;
int n,m;
int f[N],d[N];
int ans;
int find(int x)//找到x所在的集合,并且更新x所在集合中所有点的边权
{
if(x!=f[x])
{
int root=find(f[x]);
d[x]+=d[f[x]];
f[x]=root;
}
return f[x];
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)f[i]=i;
while(m--)
{
int op,x,y;
scanf("%d%d%d",&op,&x,&y);
if(x>n||y>n)ans++;
else if(x==y&&op==2)ans++;
else
{
int fx=find(x),fy=find(y);
int t=0;//初始时默认x和y是同类,那么边权模3应该是相等的
if(op==2)t=1;//如果是x吃y,那么x模3后的边权应该比y模3后的边权大1
if(fx!=fy)//如果x和y不在一个集合中,就合并集合,并且更新边权
{
f[fx]=fy;//将x的根节点指向y的根节点
d[fx]=d[y]-d[x]+t;//更新x的根节点到y的根节点的距离
}
else//如果x和y在一个集合中,就判断是否有矛盾
{
if(abs(d[x]-d[y]-t)%3)
ans++;
}
}
}
printf("%d",ans);
return 0;
}
6,真正的骗子
题目链接:https://www.acwing.com/problem/content/261/
这题有些复杂,需要用到带权并查集+01背包+DP回溯
同样的在算法进阶指南中给出了解释,高级数据结构(算法进阶指南)
代码如下:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<unordered_map>
#include<vector>
using namespace std;
const int N=1010;
int n,p1,p2;//p1为天使数,p2为恶魔数
int a,b;
char str[5];
//f[i]表示点i所在的集合,d[i]表示点i的边权,dp[i][j]表示在前i个连通块中选择,能凑成j个天使的方案数
//num[i][0]表示第i个连通块中边权为0的数量,num[i][1]表示第i个连通块中边权为1的数量
int f[N],d[N],dp[N][N],num[N][2];
int find(int x)//找到x所在的集合,并且更新集合中所有点的边权且将集合中所有点路径压缩,指向根节点
{
if(x!=f[x])
{
int root=find(f[x]);
d[x]^=d[f[x]];
f[x]=root;
}
return f[x];
}
int main()
{
while(scanf("%d%d%d",&n,&p1,&p2),n||p1||p2)//多组测试数据
{
//初始化
for(int i=1;i<=p1+p2;i++)f[i]=i,d[i]=0;
memset(dp,0,sizeof dp);
memset(num,0,sizeof num);
//输入数据,合并集合,更新集合中点的边权
for(int i=1;i<=n;i++)
{
scanf("%d%d%s",&a,&b,str);
int t=0;//初始时默认时yes,表示a和b的边权相同
if(str[0]=='n')t=1;//如果是no,表示a和b的边权不同
int fa=find(a),fb=find(b);//找到a的根节点和b的根节点
f[fa]=fb;//合并集合
d[fa]=d[a]^d[b]^t;//更新边权
}
int cnt=0;//记录连通块的数量
unordered_map<int,int>mp;//用map给每个连通块编号
for(int i=1;i<=p1+p2;i++)
{
int x=find(i);
if(mp.count(x)==0)mp[x]=++cnt;//如果当前连通块之前没出现过,就编一个号
num[mp[x]][d[i]]++;//记录当前连通块中不同边权的数量
}
//DP过程
dp[0][0]=1;//边界初始化,从前0个连通块中选择,能凑出0个人的方案数为1
for(int i=1;i<=cnt;i++)//遍历连通块的数量
{
for(int j=0;j<=p1;j++)//遍历天使的数量
{
if(j>=num[i][0])dp[i][j]+=dp[i-1][j-num[i][0]];//如果当前天使的数量能由第i个连通块中边权为0的人数凑成的话
if(j>=num[i][1])dp[i][j]+=dp[i-1][j-num[i][1]];//如果当前天使的数量能由第i个连通块中边权为1的人数凑成的话
}
}
//DP回溯,找到转移的过程
//如果方案数不等于1,说明不满足题意,因为如果方案数大于1
//那么至少会有一处不同,不能确定不同的那处的连通块中谁为天使,谁为恶魔
if(dp[cnt][p1]!=1)puts("no");
else
{
bool ans[N][2];//用一个ans记录答案
memset(ans,false,sizeof ans);
for(int i = cnt, j =p1; i >= 1; i --)//倒着查找转移的过程
{
if(dp[i - 1][j - num[i][0]] == 1)//说明由第i个连通块中边权为0的方案数转移过来的;
{
ans[i][0] = true;
j -= num[i][0];
}
else if(dp[i - 1][j - num[i][1]] == 1)//说明由第i个连通块中边权为1的方案数转移过来的
{
ans[i][1] = true;
j -= num[i][1];
}
}
for(int i = 1; i <= p1+p2; i ++)//先找到第i个人所在的连通块和边权,看这个连通块的此边权是否时转移的点
if(ans[mp[find(i)]][d[i]])
cout << i << endl;
cout << "end" << endl;
}
}
return 0;
}
7,超市
题目链接:https://www.acwing.com/problem/content/147/
其实这题用优先队列会更加好做,做法写在注释里了
代码如下:
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
const int N=10010;
typedef pair<int,int>pii;
pii thing[N];
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
for(int i=0;i<n;i++)
{
int v,s;
scanf("%d%d",&v,&s);
thing[i]={s,v};
}
sort(thing,thing+n);//先将商品按时间排好序
priority_queue<int,vector<int>,greater<int>>heap;//堆里存储的是我们要卖出的商品的利润
for(int i=0;i<n;i++)
{
int v=thing[i].second,s=thing[i].first;
heap.push(v);
if(s<heap.size())//如果这件商品的时间小于已经计划卖出的商品数,就删除堆顶的商品,也就是删除利润最低的商品
heap.pop();
}
int res=0;
while(heap.size())//计算堆里面所有的商品利润
{
res+=heap.top();
heap.pop();
}
printf("%d\n",res);
}
return 0;
}
8,奇偶游戏
题目链接:https://www.acwing.com/problem/content/241/
这题同样使用带权并查集维护两中集合的情况,可以用异或处理边权,在算法进阶指南写过了
代码如下:
#include<iostream>
#include<algorithm>
#include<unordered_map>
using namespace std;
const int N=5010,M=N*2;
int n,m,cnt;//cnt用于给每个离散化后的点编号
int f[M],d[M];//d表示该节点与其父节点的奇偶性是否相同,0表示相同,1表示不同,f为并查集
unordered_map<int,int>mp;//哈希表用于离散化
int find(int x)//找到每个集合的根节点,并且将路径上的点都指向根节点,同时修改路径上的点的权值
{
if(f[x]!=x)
{
int root=find(f[x]);
d[x]^=d[f[x]];
f[x]=root;
}
return f[x];
}
int get(int x)//离散化
{
if(mp.count(x)==0)mp[x]=++cnt;//如果之前这个点没被离散化过,就开一个新的编号给它
return mp[x];//返回这个点的编号
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=M;i++)f[i]=i;//并查集初始化
int res=m;//初始时将答案设为m,这样如果全部都没错误的话m就是答案
for(int i=1;i<=m;i++)
{
int a,b;
char str[5];
scanf("%d%d%s",&a,&b,str);
a=get(a-1),b=get(b);//首先要离散化
int fa=find(a),fb=find(b);//离散化后找到该点的根节点,也就是该点所在的集合
int t=0;//t表示奇偶性,0表示偶数
if(str[0]=='o')t=1;//1表示奇数
if(fa==fb)//如果这两个点在一个集合中,就可以知道这两个点的关系
{
if((d[a]^d[b])!=t)//如果这两个点的奇偶性和给定的条件的奇偶性冲突了,就找到答案了
{
res=i-1;
break;
}
}
else//如果这两个点不在一个集合中,就将他们合并到一个集合中
{
f[fa]=fb;//将a的根节点指向b的根节点
d[fa]=d[a]^d[b]^t;//根性a的根节点到b的根节点的距离
}
}
printf("%d",res);//输出答案
return 0;
}
9,导航噩梦
题目链接:https://www.acwing.com/problem/content/4290/
这题需要用带权并查集维护x轴和y轴方向上的边权,对于给定a,b,如果b在a的东边,我们就记录为x轴上的正边权,如果在西边,就记录x轴上的负边权,如果b在a的北边,就记录y轴上的正边权,如果b在a的南边,就记录y轴上的负边权。同时,我们将询问升序排序,这样就可以在合并集合时一起处理询问的问题,以及这个题可能会出现重复询问的情况,所以处理询问时要用一个while循环处理
代码如下:
#include<iostream>
#include<algorithm>
#include<cmath>
#define x first
#define y second
using namespace std;
const int N=40010;
typedef pair<int,int>pii;
int n,m,k;
int f[N],ans[N];//ans记录答案
pii d[N];//注意这题要维护两个边权,一个表示该节点到其父节点的x轴上的距离,另一个表示y轴上的距离
struct Node//存储给的农场的信息
{
int a,b,len,op;//op为0说明是x轴,op为1说明是y轴
}node[N];
struct Query//存储给的询问的信息
{
int a,b,num,id;//id记录是第几个询问,因为后面会排序
}query[N];
bool cmp(Query& x,Query& y)//按num来从小到大排序
{
return x.num<y.num;
}
int find(int x)//找到x所在的集合,并且更新x所在集合中点的边权,并将其指向根节点
{
if(x!=f[x])
{
int root=find(f[x]);
d[x].x+=d[f[x]].x;//更新x到其父节点x轴上的距离
d[x].y+=d[f[x]].y;//更新x到其父节点y轴上的距离
f[x]=root;
}
return f[x];
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)f[i]=i;
for(int i=1;i<=m;i++)//出入农场的信息
{
int a,b,len,t;
char op[2];
scanf("%d%d%d%s",&a,&b,&len,op);
//记录下来是x轴上的偏移量还是y轴上的偏移量,对应的距离取正还是取负
if(op[0]=='E')node[i]={a,b,len,0};
else if(op[0]=='W')node[i]={a,b,-len,0};
else if(op[0]=='N')node[i]={a,b,len,1};
else node[i]={a,b,-len,1};
}
scanf("%d",&k);
for(int i=1;i<=k;i++)//输入询问,并且记录下来每个询问的编号
{
int a,b,num;
scanf("%d%d%d",&a,&b,&num);
query[i]={a,b,num,i};
}
sort(query+1,query+1+k,cmp);//按num来从小到大排序
int idx=1;//用来遍历询问
for(int i=1;i<=m;i++)
{
int a=node[i].a,b=node[i].b,len=node[i].len,t=node[i].op;
int fa=find(a),fb=find(b);
if(fa!=fb)//不在一个集合首先将其合并到一个集合中
{
f[fa]=fb;//将a的根节点指向b的根节点
if(t==0)//说明a和b在x轴上,更新x轴上的距离与更新y轴上的距离的方式不同
{
d[fa].x=d[b].x-d[a].x+len;
d[fa].y=d[b].y-d[a].y;
}
else//说明a和b在y轴上,同样的,要注意更新x轴上的距离与更新y轴上的方式不同
{
d[fa].x=d[b].x-d[a].x;
d[fa].y=d[b].y-d[a].y+len;
}
}
while(query[idx].num==i)因为可能有多个询问的num是i,所以这里要用一个while循环
{
a=query[idx].a,b=query[idx].b;
int id=query[idx].id;
fa=find(a),fb=find(b);
if(fa!=fb)ans[id]=-1;//如果a和b不在一个集合中,就说明不能找到答案
else ans[id]=abs(d[a].x-d[b].x)+abs(d[a].y-d[b].y);//如果在一个集合中,就可以得到答案
idx++;
}
}
for(int i=1;i<=k;i++)
printf("%d\n",ans[i]);
return 0;
}
10,研究虫子
题目链接:https://www.acwing.com/problem/content/4291/
这题用带权并查集维护两中集合,假设一种集合的边权到根节点的边权为1,另一种为0,因此只能是不同性交配,所以对于给定的信息,如果不知道这两个虫子的关系的话,我们就将其加入到一个集合中,但是边权要不同,如果两个虫子已经在一个集合中了,我们就可以知道这两个虫子的关系,如果这两个虫子的边权都为0或都为1,说明这两个虫子是同性
代码如下:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=2010;
int n,m;
int f[N],d[N];//d[i]表示点i到其父节点的边权
int find(int x)//找到x所在的集合,并更新x所在集合中的点的边权
{
if(x!=f[x])
{
int root=find(f[x]);
d[x]^=d[f[x]];
f[x]=root;
}
return f[x];
}
int main()
{
int t;
scanf("%d",&t);
for(int i=1;i<=t;i++)
{
printf("Scenario #%d:\n",i);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)f[i]=i,d[i]=0;//初始化
bool flag=true;//如果为false说明发生了同性交配
while(m--)
{
int a,b;
scanf("%d%d",&a,&b);
int fa=find(a),fb=find(b);
if(fa!=fb)//不在一个集合就合并成一个集合
{
f[fa]=fb;//将a的根节点指向b的根节点
d[fa]=d[a]^d[b]^1;//更新a的根节点到b的根节点的距离
}
else//在一个集合就判断是否冲突
{
if(d[a]^d[b]==0)
flag=false;
}
}
if(!flag)
printf("Suspicious bugs found!\n");
else
printf("No suspicious bugs found!\n");
printf("\n");
}
return 0;
}
11,石头剪刀布
题目链接:https://www.acwing.com/problem/content/description/260/
这题算法进阶指南里讲了高级数据结构(算法进阶指南) ,维护点的边权与食物链相似
代码如下:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int N=510,M=2010;
int n,m,num;
int f[N],d[N],t[N];
int a[M],c[M];
char b[M];
int find(int x)//找到x所在集合的根节点,并更新集合中其他点的边权和其父节点,将其父节点指向根节点,即路径压缩
{
if(x!=f[x])
{
int root=find(f[x]);
d[x]+=d[f[x]];
f[x]=root;//!!!!!注意,这里一定要写在最后,否则上一步的边权修改会出错
}
return f[x];
}
bool check(int j)//判断第j轮对局是否满足条件
{
int x=a[j],y=c[j];
int fx=find(x),fy=find(y);//分别找到x所在的集合和y所在的集合
int op=0;//初始假定它们相等
if(b[j]=='>')op=1;//如果x大于y,op为1
else if(b[j]=='<')op=-1;//如果x小于y,op为-1
//如果x和y之前已经在一个集合中,在集合中的关系与给定的关系出现了矛盾,就说明第j轮对局出现了矛盾
//注意这里的判断条件,一定要这么写,可能会出现负数,但是不用取绝对值,因为我们只需要知道%3后是否等于0
if(fx==fy&&(d[x]-d[y]-op)%3)return false;
else
{
f[fx]=fy;//将x的根节点指向y的根节点
d[fx]=d[y]-d[x]+op;//更新x的根节点到y的根节点的距离
}
return true;//说明第j轮符合条件
}
int main()
{
while(cin>>n>>m)
{
for(int i=1;i<=m;i++)
cin>>a[i]>>b[i]>>c[i];
memset(t,0,sizeof t);//多组测试数据,每组要初始化
int cnt=0;//用于记录可能是裁判的人数
for(int i=0;i<n;i++)//枚举每个人作为裁判时
{
for(int i=0;i<=n;i++)f[i]=i,d[i]=0;//枚举每个人都要初始化
bool is_umpire=true;//首先假定第i个人可以作为裁判
for(int j=1;j<=m;j++)//枚举每一轮的对局
{
if(a[j]!=i&&c[j]!=i&&!check(j))//判断不包含裁判的对局是否会出现冲突,如果出现冲突说明第i个人不为裁判
{
is_umpire=false;
t[i]=j;
break;
}
}
if(is_umpire)
{
cnt++;
num=i;
}
}
if(!cnt)puts("Impossible");
else if(cnt>1)puts("Can not determine");
else
{
int res=0;
for(int i=0;i<n;i++)
if(i!=num)
res=max(res,t[i]);
printf("Player %d can be determined to be the judge after %d lines\n",num,res);
}
}
return 0;
}
12,连接问题
题目链接:https://www.acwing.com/problem/content/4292/
这题我们只用一个朴素并查集就可以维护,维护时,每次合并区间我们都选择权值较大的点作为根节点,如果权值相等,就选择将编号较小的首领作为根节点,然后我们用一个pair记录下来所有的操作,如果时查询操作,pair的第二关键字就为-1,同时我们记录下来会摧毁的边,然后对于不会摧毁的边先合并到一个集合中,逆序处理存储的所有操作,如果是查询操作就查询目前的集合,目前的集合等价于正序枚举时前面操作摧毁给定边后的集合,用一个数组存下答案,最终再逆序输出答案,就得到的是正向遍历的答案
代码如下:
#include<iostream>
#include<algorithm>
#include<set>
using namespace std;
const int N = 10010, M = (N << 1);
typedef pair<int,int>pii;
int n, m, q, val[N], fa[N];
pii e[M],query[50010];
int stk[50010], top; //栈
int find(int x){
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
void merge(int x, int y){
int fx = find(x), fy = find(y);
if(fx != fy){
if(val[fx] < val[fy]) swap(fx, fy); //将权值较大的作为首领
else if(val[fx] == val[fy]) {
if(fx > fy) swap(fx, fy); //若权值相等,编号小的作为首领
}
fa[fy] = fx;
}
}
int main(){
bool flag = true;
while(cin >> n){
if(flag) flag = false;
else puts(""); //输出格式
set<pii> st;//存储会被摧毁的边
top = 0;
for(int i = 0; i < n; i ++ ) fa[i] = i, cin >> val[i];
//存储所有的边
cin >> m;
for(int i = 1; i <= m; i ++ ){
int x, y; cin >> x >> y;
if(x > y) swap(x, y);
e[i] = {x, y};
}
//存储询问
cin >> q;
for(int i = 1; i <= q; i ++ ){
string op; cin >> op;
int x, y;
if(op == "query") cin >> x, query[i] = {x, -1};
else {
cin >> x >> y;
if(x > y) swap(x, y);
query[i] = {x, y}, st.insert({x, y});
}
}
//先建立所有不会被摧毁的边
for(int i = 1; i <= m; i ++ )
if(!st.count(e[i]))
merge(e[i].first, e[i].second);
//逆序处理询问
for(int i = q; i; i -- )
if(query[i].second == -1){
int res = find(query[i].first);
if(val[res] <= val[query[i].first]) stk[ ++ top] = -1;//不存在比当前大的
else stk[ ++ top] = res;
}else merge(query[i].first, query[i].second); //合并
//逆序输出答案
while(top) printf("%d\n", stk[top -- ]);
}
return 0;
}
13,小希的迷宫
题目链接:https://www.acwing.com/problem/content/description/4293/
这题只需要一个朴素并查集就可以做,但是有很多细节问题,首先要判断一个图是否是数,我们只需要看是否是n个点n-1条边,且只有一个根节点,同时没有自环,然后有一个特判,0个点0条边也是树
对于给定的两个点,存在一条边,我们就将其加入到一个集合中,如果对于给定的两个点已经在一个集合,说明有环,那么就不是一个树,最后要判断一下是否只存在一个根节点
代码如下:
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N=1e5+10;
int f[N];
int find(int x)
{
if(x!=f[x])f[x]=find(f[x]);
return f[x];
}
int main()
{
int a,b;
while(scanf("%d%d",&a,&b)!=EOF)
{
if(a==-1&&b==-1)
break;
if(a==0&&b==0)//这也是一个样例
puts("Yes");
else
{
vector<int>v;//用一个vector记录下来出现过的编号,后面用于求该树是否只有一个根节点
for(int i=0;i<=N;i++)f[i]=i;//并查集初始化
bool flag=true;//判断是否是树
int fa=find(a),fb=find(b);
f[fa]=fb;//先将连了边的两个点合并到一个集合中
v.push_back(a),v.push_back(b);//加入到vector中
while(scanf("%d%d",&a,&b),a||b)
{
if(!flag)continue;//这里要注意!!!!!这题卡常数严重,要加一个优化才能过,否则会MLE
//也就是说只要判断出来了该图不是树,那么久不用将后续的点加入到vecotr中了
v.push_back(a),v.push_back(b);
fa=find(a),fb=find(b);
if(fa==fb)
flag=false;
else
f[fa]=fb;
}
if(flag)//判断是否只有一个根节点
{
for(int i=0;i<v.size();i++)
if(find(v[i])!=find(v[0]))
flag=false;
}
if(flag)
puts("Yes");
else
puts("No");
}
}
return 0;
}
14,这是一颗树吗
题目链接:https://www.acwing.com/problem/content/3412/
这题跟上一题差不多,代码也基本一样
代码如下:
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;
const int N=1e4+10;
int f[N],c[N];
int find(int x)
{
if(x!=f[x])f[x]=find(f[x]);
return f[x];
}
int main()
{
int a,b;
int t=0;
while(scanf("%d%d",&a,&b)!=EOF)
{
memset(c,0,sizeof c);
t++;
if(a==-1&&b==-1)
break;
if(a==0&&b==0)//这也是一个样例,表示这课树只有一个根节点
printf("Case %d is a tree.\n",t);
else
{
vector<int>v;//用一个vector记录下来出现过的编号,后面用于求该树是否只有一个根节点
for(int i=0;i<=N;i++)f[i]=i;//初始化
bool flag=true;//判断是否是树
int fa=find(a),fb=find(b);
f[fa]=fb;//先将连了边的两个点合并到一个集合中
v.push_back(a),v.push_back(b);//加入到vector中
c[b]++;
while(scanf("%d%d",&a,&b),a||b)
{
if(!flag)continue;//这里要注意!!!!!这题卡常数严重,要加一个优化才能过,否则会MLE
//也就是说只要判断出来了该图不是树,那么久不用将后续的点加入到vecotr中了
c[b]++;
if(c[b]>1)
{
flag=false;
continue;
}
v.push_back(a),v.push_back(b);
fa=find(a),fb=find(b);
if(fa==fb)//如果a和b点已经在一个集合中,还要连边的话,说明有环那么就不是一颗树了
flag=false;
else//否则合并集合
f[fa]=fb;
}
if(flag)//如果前面判断出来无环,我们还要再判断这棵树是否只有一个根
{
for(int i=0;i<v.size();i++)//只需要判断是否存在不一样的根节点即可
if(find(v[i])!=find(v[0]))
flag=false;
}
if(flag)
printf("Case %d is a tree.\n",t);
else
printf("Case %d is not a tree.\n",t);
}
}
return 0;
}