算法学习周记4(acwing 并查集、二分)

acwing——836 合并并查

一共有 nn 个数,编号是 1∼n1∼n,最开始每个数各自在一个集合中。

现在要进行 mm 个操作,操作共有两种:

  1. M a b,将编号为 aa 和 bb 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
  2. Q a b,询问编号为 aa 和 bb 的两个数是否在同一个集合中;

输入格式

第一行输入整数 nn 和 mm。

接下来 mm 行,每行包含一个操作指令,指令为 M a b 或 Q a b 中的一种。

输出格式

对于每个询问指令 Q a b,都要输出一个结果,如果 aa 和 bb 在同一集合内,则输出 Yes,否则输出 No

每个结果占一行。

数据范围

1≤n,m≤1051≤n,m≤105

输入样例:

4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4

输出样例:

Yes
No
Yes

ac代码:

#include <stdio.h>
#define max 100010
int n,m,a,b;
char x;
int num[max];
int renew(int n){
	int i = 1;
	for(;i<=n;i++)
		num[i] = i;
}
int find(int x){
	if(num[x]!=x){
		num[x] = find(num[x]);
	}
	return num[x];
}
void uni(int x,int y){
	int f1,f2;
	f1 = find(x);
	f2 = find(y);
	num[f1] = f2;
}
int main(){
	scanf("%d %d",&n,&m);
	int f1,f2;
	renew(n);
	while(m--){
		getchar();
		scanf("%c %d %d",&x,&a,&b);
		if(x=='M'){
			uni(a,b);	
		}
		if(x=='Q'){
			f1 = find(a);
			f2 = find(b);
			if(f1==f2){
				printf("Yes\n");
			}
			else
				printf("No\n");
		}
	}
	return 0;
} 

acwing——837 连接块中点的数量

给定一个包含 nn 个点(编号为 1∼n1∼n)的无向图,初始时图中没有边。

现在要进行 mm 个操作,操作共有三种:

  1. C a b,在点 aa 和点 bb 之间连一条边,aa 和 bb 可能相等;
  2. Q1 a b,询问点 aa 和点 bb 是否在同一个连通块中,aa 和 bb 可能相等;
  3. Q2 a,询问点 aa 所在连通块中点的数量;

输入格式

第一行输入整数 nn 和 mm。

接下来 mm 行,每行包含一个操作指令,指令为 C a bQ1 a b 或 Q2 a 中的一种。

输出格式

对于每个询问指令 Q1 a b,如果 aa 和 bb 在同一个连通块中,则输出 Yes,否则输出 No

对于每个询问指令 Q2 a,输出一个整数表示点 aa 所在连通块中点的数量

每个结果占一行。

数据范围

1≤n,m≤1051≤n,m≤105

输入样例:

5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5

输出样例:

Yes
2
3

/*解题思路:

        解读题意:在原有的并查集基础上加上了查询该集合一共有多少元素的功能;

        只需要用一个数组用来存储和修改即可;

        当合并两个数的时候,偏向于一个数去连接分支,然后该数所携带的元素的和加上分支上的总数;

        当需要到查询q2功能的时候,查找根节点,输出根节点所连接的点的总数即可

*/

ac代码:

#include <stdio.h>
#define max 100010
int n,m,a,b;
char x[3];
int num[max],good[max];
int renew(int n){
	int i = 1;
	for(;i<=n;i++){
		num[i] = i;
		good[i] = 1;
	}	
}
int find(int x){
	if(num[x]!=x){
		num[x] = find(num[x]);
	}
	return num[x];
}
void uni(int x,int y){
	int f1,f2;
	f1 = find(x);
	f2 = find(y);
	num[f1] = f2;
	if(f1!=f2){
		good[f2]+=good[f1];
	}
}
int main(){
	scanf("%d %d",&n,&m);
	int f1,f2;
	renew(n);
	while(m--){
		getchar();
		scanf("%s",x);
		if(x[0]=='C'){
			scanf("%d %d",&a,&b);
			uni(a,b);	
		}
		else if(x[1]=='1'){
			scanf("%d %d",&a,&b);
			f1 = find(a);f2 = find(b);
			if(f1==f2){
				printf("Yes\n");
			}
			else{
				printf("No\n");
			}
		}
		else if(x[1]=='2'){
			scanf("%d",&a);
			f1 = find(a);
			printf("%d\n",good[f1]);
		} 
	}
	return 0;
} 

acwing-240 食物链

动物王国中有三类动物 A,B,CA,B,C,这三类动物的食物链构成了有趣的环形。

AA 吃 BB,BB 吃 CC,CC 吃 AA。

现有 NN 个动物,以 1∼N1∼N 编号。

每个动物都是 A,B,CA,B,C 中的一种,但是我们并不知道它到底是哪一种。

有人用两种说法对这 NN 个动物所构成的食物链关系进行描述:

第一种说法是 1 X Y,表示 XX 和 YY 是同类。

第二种说法是 2 X Y,表示 XX 吃 YY。

此人对 NN 个动物,用上述两种说法,一句接一句地说出 KK 句话,这 KK 句话有的是真的,有的是假的。

当一句话满足下列三条之一时,这句话就是假话,否则就是真话。

  1. 当前的话与前面的某些真的话冲突,就是假话;
  2. 当前的话中 XX 或 YY 比 NN 大,就是假话;
  3. 当前的话表示 XX 吃 XX,就是假话。

你的任务是根据给定的 NN 和 KK 句话,输出假话的总数。

输入格式

第一行是两个整数 NN 和 KK,以一个空格分隔。

以下 KK 行每行是三个正整数 D,X,YD,X,Y,两数之间用一个空格隔开,其中 DD 表示说法的种类。

若 D=1D=1,则表示 XX 和 YY 是同类。

若 D=2D=2,则表示 XX 吃 YY。

输出格式

只有一个整数,表示假话的数目。

数据范围

1≤N≤500001≤N≤50000,
0≤K≤1000000≤K≤100000

输入样例:

100 7
1 101 1 
2 1 2
2 2 3 
2 3 3 
1 1 3 
2 3 1 
1 5 5

输出样例:

3

ac代码:

#include <stdio.h>
#define max 100010
int n,m,x,y,sum=0,d;
int num[3*max];
int renew(int n){
	int i = 1;
	for(;i<=3*n;i++){
		num[i] = i;
	}	
}
int find(int x){
	if(num[x]!=x){
		num[x] = find(num[x]);
	}
	return num[x];
}
void uni(int x,int y){
	int f1,f2;
	f1 = find(x);
	f2 = find(y);
	num[f1] = f2;
}
int main(){
	scanf("%d %d",&n,&m);
	int f1,f2;
	renew(n);
	while(m--){
		scanf("%d %d %d",&d,&x,&y);
		if(x>n||y>n){
			sum++;
			continue;
		}
		f1=find(x);
		f2=find(y);
		if(d==1){
			if(f1==f2){
				continue;
			}
			if(f1==find(y+n)||f1==find(y+n+n)){
				sum++;
				continue;
			}
			else{
				uni(x,y);
				uni(x+n,y+n);
				uni(x+n+n,y+n+n);
			}
		}
		else if(d==2){
			if(x==y){
				sum++;
				continue;
			}
			if(f1==find(y+n)){
				continue;
			}
			else if(f1==f2||f1==find(y+n+n)){
				sum++;
				continue;
			}
			else{
				uni(x,y+n);
				uni(x+n,y+n+n);
				uni(x+n+n,y);
			}
		}
	}
	printf("%d\n",sum);
	return 0;
} 

acwing-789 数的范围

给定一个按照升序排列的长度为 nn 的整数数组,以及 qq 个查询。

对于每个查询,返回一个元素 kk 的起始位置和终止位置(位置从 00 开始计数)。

如果数组中不存在该元素,则返回 -1 -1

输入格式

第一行包含整数 nn 和 qq,表示数组长度和询问个数。

第二行包含 nn 个整数(均在 1∼100001∼10000 范围内),表示完整数组。

接下来 qq 行,每行包含一个整数 kk,表示一个询问元素。

输出格式

共 qq 行,每行包含两个整数,表示所求元素的起始位置和终止位置。

如果数组中不存在该元素,则返回 -1 -1

数据范围

1≤n≤1000001≤n≤100000
1≤q≤100001≤q≤10000
1≤k≤100001≤k≤10000

输入样例:

6 3
1 2 2 3 3 4
3
4
5

输出样例:

3 4
5 5
-1 -1

思路:!!!在于二分查找的过程之中的中点的等于条件来查找左右边界

       左边界不加1,右边界+1

ac代码:

//查找数的左右边界
//运用二分法查找需要是严格上升数列 
#include <stdio.h>
#define N 100010
int n,m,x;
int q[N];
int main()
{
	int i,l,r,mid;
    scanf("%d %d",&n,&m);
    for(i=0; i<n; i++) scanf("%d",&q[i]);
    
    while(m--)
    {
        scanf("%d",&x);
        l = 0; r = n -1;
        while(l < r)
        {
        	// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时, 不必+ 1
            mid = ( l + r )/2;
            if(q[mid] >= x) r = mid;
            else l = mid + 1;
        }
        if(q[l] != x) 
        	printf("-1 -1\n");
        else
        {
            printf("%d ",l);
            l =0; r = n - 1;
            while(l < r)
            {
	            // 区间[l, r]被划分成[l, mid - 1]和[mid, r]时, 需要 + 1
                mid = ( l + r + 1 ) / 2; 
                if(q[mid] <= x) l = mid;
                else r = mid - 1;
            }
            printf("%d\n",l);
        }
        
    }
    return 0;
}

acwing - 790 数的三次方根

给定一个浮点数 nn,求它的三次方根。

输入格式

共一行,包含一个浮点数 nn。

输出格式

共一行,包含一个浮点数,表示问题的解。

注意,结果保留 66 位小数。

数据范围

−10000≤n≤10000−10000≤n≤10000

输入样例:

1000.00

输出样例:

10.000000

ac代码:

#include <stdio.h>
/*思路:
	1. 二分查找,从数据mid = 0开始查找,
	如果满足条件mid的三次方大于n或者小于0,逐渐缩小范围
	2.输出最后查找出来的值就是n的三次方根 

*/
double n,l,r,mid;
double s(double a){
	return a*a*a;
}
int main()
{
	l = -10000;
	r = 10000;
	scanf("%lf",&n);
	while(r-l > 1e-8){
		mid = (l+r)/2;
		if(s(mid)>=n) r = mid;
		else l = mid;
	}
	printf("%.6lf",l);
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值