2021-02-02学习总结
早上8:00学习打卡
早上9:00 – 14:00(5h)
学习并查集并且搞定题组
A - 一张桌子到底坐了几个人!
544的100大寿马上就要来临了,现在他要举办一场宴席,邀请了各路来宾前来祝寿。无奈的是,544的退休金并不是很多,因此需要尽可能使得宴席的桌数少。
但是544的朋友们也很挑剔,他们不会跟陌生人坐在一起,但是却可以跟朋友的朋友坐在一起。
具体来说,如果A认识B,B认识C,C认识D,E认识F,那么A,B,C,D可以坐在一起,E和F可以坐在一起,这样安排的桌数是2。显然,E或F不能与A,B,C,D坐在一起。
由于544已经100岁了,脑袋不足以支持太过复杂的运算,所以他邀请了你来解决这个问题:给出544的朋友之间的认识关系,请问最少需要摆多少张桌子呢?
Input
输入以一个整数T开始 (1<=T<=25) 表示测试数据组数. 接下来是T个测试数据。 每个测试数据以两个整数N和M开始(1<=N,M<=1000). N表示朋友的数目,编号为1~N。接下来是M行,每行包含两个整数A和B(A!=B),表示A和B互相是朋友。每两组测试数据间以一个空行相隔。
Output
对于每组测试数据,输出一个整数,代表最少需要摆放的桌数。
Sample Input
2
5 3
1 2
2 3
4 5
5 1
2 5
Sample Output
2
4
非常基础的并查集,因为我是看完啊哈算法的并查集,还有B站的正月点灯笼的视频,啊哈上的P200与这题是类似的,不能说完全不同,只能说是一模一样,上AC代码
#include<iostream>
#include<cmath>
#include<map>
#include<queue>
#include<stack>
#include<algorithm>
#include<stdlib.h>
using namespace std;
int n,m,parent[100000];
void initialise()
{
int i;
for(i=1;i<=n;i++){
parent[i]=i;
}
}
int find_root(int x){//log(n);
if(parent[x]==x)
return x;
else{
//压缩路径
parent[x]=find_root(parent[x]);
return parent[x];
}
}
void union_vertices(int x,int y){
int x_root = find_root(x);
int y_root = find_root(y);
if(x_root!=y_root)
{
parent[y_root]=x_root;//靠左原则;
}
}
int main()
{
int i,x,y,T;
cin>>T;
while(T--){
int cnt=0;
cin>>n>>m;
initialise();//初始化
for(i=1;i<=m;i++){
cin>>x>>y;
union_vertices(x,y);
}
for(i=1;i<=n;i++){
if(parent[i]==i){
cnt++;
}
}
printf("%d\n",cnt );
}
}
B - 宝可梦到底有多少钉子!
代学长的宝可梦牧场中只有三种属性的宝可梦,水、火、草。这三种属性互相克制。(水克火,火克草,草克水)
现有N个宝可梦,以1-N编号,但是代学长并不知道每个宝可梦是什么属性。
孟学长调查了牧场之后,用两种说法对这N个宝可梦的属性进行描述:
第一种说法是"1 X Y",表示X和Y是同属性。
第二种说法是"2 X Y",表示X克Y。
孟学长对N个宝可梦,用上述两种说法,一句接一句地说出K句话,但是孟学长经常犯错,K句话有对有错。当一句话满足下列三条之一时,这句话就是错的,否则就是对的。
1) 当前的话与前面的某些对的话冲突,就是错的;
2) 当前的话中X或Y比N大,就是错的;
3) 当前的话表示X克X,就是错的。
孟学长每犯错一次就会收获一枚钉子,请根据给定的N(1 <= N <= 50,000)和K(0 <= K <= 100,000)句话,算出孟学长收获钉子的数量。
Input
第一行是两个整数N和K,以一个空格分隔。
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。
若D=1,则表示X和Y是属性。
若D=2,则表示X克Y。
Output
只有一个整数,表示钉子的数目。
Sample Input
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
Sample Output
3
ko no POKEMEN大师哒。
这题是我个人觉得三题里比较麻烦的,说说我的题解。
下标i表示i为水属性, i+n表示i为火属性, i+2 * n表示i为草属性,因为并不知道每个pokemen是个什么属性,所以需要假设。所以当x和y为同一属性的时候,把x,y和x+n,y+n和x+2n,y+2n合并成一个物种。
同理,当x克y时,把x,y+2n, x+2n和y+n,x+n和y合并;
在合并之前判断一下是否矛盾,
例如合并x,y之前判断一下x克y或y克x是不是存在。
然后,还没有写的同组的同学们,这题不是多组输入!!!;
上AC
#include<iostream>
#include<cmath>
#include<map>
#include<queue>
#include<stack>
#include<algorithm>
#include<stdlib.h>
#include<stdio.h>
using namespace std;
int n,k,parent[300030];
void initialise()
{
int i;
for(i=1; i<=3*n; i++)
{
parent[i]=i;
}
}
int find_root(int x) //log(n);
{
if(x!=parent[x])
{
parent[x]=find_root(parent[x]);
}
return parent[x];
}
void union_vertices(int x,int y)
{
int x_root = find_root(parent[x]);
int y_root = find_root(parent[y]);
parent[x_root]=y_root;
}
int main()
{
//下标i表示i为水属性, i+n表示i为火属性, i+2*n表示i为草属性,因为并不知道每个pokemen是个什么属性,所以需要假设。
int x,y,flag;
scanf("%d%d",&n,&k);
int cnt=0;
initialise();//初始化
while(k--)
{
scanf("%d%d%d",&flag,&x,&y);
if(x>n||y>n)//2) 当前的话中X或Y比N大,就是错的;
//3) 当前的话表示X克X,就是错的。
{
cnt++;
}
else
{
if(flag==1)//对关系1讨论
{
if(find_root(x+n)==find_root(y)||find_root(x+2*n)==find_root(y))//如果x是y的克制或被克制,显然为谎言
cnt++;
else
{
union_vertices(x,y);
union_vertices(x+n,y+n);
union_vertices(x+2*n,y+2*n);
//如果为真,那么x的同类是y的同类,x的克制是y的克制,x的被克制是y的被克制
}
}
else
{
if(x==y||find_root(x)==find_root(y)||find_root(x+2*n)==find_root(y))
{
cnt++;
}//如果1是2的同类或克制,显然为谎言
else
{
union_vertices(x,y+2*n);
union_vertices(x+n,y);
union_vertices(x+2*n,y+n);
如果为真,那么x的同类是y的克制,x的克制是y的同类,x的被克制是y的克制
}
}
}
}
printf("%d\n",cnt );
}
C - 撸猫到底会不会上瘾!
事实证明,撸猫是会上瘾的
不知从何时起,猫(Cat)已经开始成为一类校园新型毒品. 无数少女少男深受其害,一天不吸,浑身难受. 而就在最近,这种生物竟开始携带一种传染性极强的流行性病毒 —— 喵病毒 (Meow Viruses). 凡是接触猫的人,都极有可能感染喵病毒. 而我们一般称那些感染喵病毒的人,犯了喵病. 喵病的发病症状十分邪魔. 初期为连续性地疯狂撸猫,晚期甚至半夜爬上房顶学猫叫! 而由于喵病毒传染性极强,它已逐渐被认为是一种全球性的威胁. 为了减少传播给别人的机会, 最好的策略就是隔离可能的患者.
在Mr.蒟蒻的大学中,有许多学生团体. 同一个团体的学生经常彼此相通,一个学生可以同时加入几个团体. 为了防止喵病毒的传播,学校收集了所有学生团体的成员名单. 应对措施如下:
一旦一个团体中有一个患者,该团内的所有的成员就都可能是患者. 为了遏制这种病毒的传播,我们需要找到所有可能的患者. 现在已知编号为0的孟同学(感染源)已经犯了喵病,请你设计程序,找出所有可能的患者!
Input
输入文件包含多组数据,对于每组测试数据:
第一行依次为两个整数N和M, 其中N是学生的数量, M是学生团体的数量.
0 < N <=30000,0 <= M <= 500。
每个学生编号是一个0到N - 1之间的整数,一开始只有0号的孟同学被视为患者.
紧随的每一行是每一个团体的成员列表. 每行有一个整数K,代表成员数量. 之后有K个整数代表这个团体的学生. 一行中的所有整数由至少一个空格隔开. N = M = 0表示输入结束,不需要处理.
Output
对于每组测试数据, 一行输出一个正整数,即可能的患者数量。
Sample Input
100 4
2 1 2
5 10 13 11 12 14
2 0 1
2 99 2
200 2
1 5
5 1 2 3 4 5
1 0
0 0
Sample Output
4
1
1
思路:用并查集来合并集合就可以了,把每个社团当作一个集合和并。最后,找出0号学生所在的集合的根节点,然后遍历所有学生,如果和0号学生的根节点相同,说明他们在一个集合当中。(0号学生真的是铁闸总)
#include<iostream>
#include<cmath>
#include<map>
#include<queue>
#include<stack>
#include<algorithm>
#include<stdlib.h>
#include<cstdio>
using namespace std;
int n,m,parent[30010],rankk[30010];
void initialise()
{
int i;
for(i=0;i<=n;i++){
parent[i]=i;
rankk[i]=0;//不设置这个数组,树就会退化成链表
}
}
int find_root(int x){//log(n);
if(parent[x]==x)
return x;
else{
parent[x]=find_root(parent[x]);
return parent[x];
}
}
void union_vertices(int x,int y){
int x_root = find_root(x);
int y_root = find_root(y);
if(x_root!=y_root)
{
if(rankk[x_root]<rankk[y_root])
parent[x_root]=y_root;//靠左原则;
else{
parent[y_root]=x_root;
if(rankk[x_root]==rankk[y_root])
rankk[x_root]++;//压缩路径
}
}
}
int main()
{
int i,x,y,t;
while(cin>>n>>m){
if(n==0&&m==0)
break;
initialise();//初始化
for(i=1;i<=m;i++){
cin>>t>>x;
for(int j=2;j<=t;j++){
cin>>y;
union_vertices(x,y);
}
}
int cnt=1;
for(i=1;i<=n;i++){
if(find_root(i)==parent[0])//这个地方不一定是i的parent有可能是parent的parent,所以应该写成find_root(i)
{
cnt++;
}
}
cout<<cnt<<endl;
}
}
下午16:00-18:00(2h)
刷星火英语的四级卷子。
晚上19:30–21:00(1h30m)
答辩。
下面是阳神留给我的问题
#include<iostream>
#include<algorithm>
#include<stdlib.h>
#include<cstring>
using namespace std;
int a[4],book[4];
void dfs(int x){
if(x==4){//全排列数组,起始等于末尾,说明已经产生了一种结果,进行输出。
for(int i=1;i<=3;i++){
cout<<a[i]<<" ";
}
cout<<endl;
return;
}
for(int i=1;i<=3;i++){
if(book[i]==0){
a[x]=i;
book[i]=1;
dfs(x+1);//对后一个元素进行全排列
book[i]=0;//回溯,book还原成未做的状态
}
}
return;
}
int main(){
memset(book,0,sizeof(book));
dfs(1);
return 0;
}