NOJ 1798

亲友团问题

时间限制(普通/Java) : 1000 MS/ 3000 MS          运行内存限制 : 65536 KByte

题目描述

在2014“华为杯”南邮大学生团体歌唱大赛每一个轮比赛现场,众多亲友团是一道亮丽的风景,他(她)们或来助威、或来观摩、或来刺探对手情报,不同亲友团之间偶尔还起冲突。为避免安全问题,主办方在赛场会划分许多独立区域,每一个区域安置一个亲友团,现在请你根据主办方提供的比赛现场信息,预测至多需要划分出多少个独立区域。

我们将主办方提供的比赛现场信息进行简化,从1开始按顺序给进入比赛现场的每位亲友分配一个编号,依次为1、2、...、K,K为亲友总数,为保护隐私,主办方只能告诉你M组两个不同亲友属于同一亲友团信息,这些信息有可能重复。

输入

输入包括多个测试用例,首先给出测试用例数N,接着给出N个测试用例,1≤N≤100。

每一个测试用例包括M+1行,首先给出两个整数K和M;再依次给出M行,每行给出两个不同正整数i和j,表示两个不同亲友i和j属于同一亲友团,i≠j,1≤i≤K,1≤j≤K,,1≤K≤10000,1≤M≤10000。

输出

输出包括多行,对于每个测试用例,输出一行,给出至多需要划分出的独立区域数量。

样例输入

2
3 1
1 2
3 2
1 2
2 3

样例输出

2
1

解题思路一

开一个数组,把属于一个亲友团的亲友赋一个相同的值,最后判断有几种不同的值就有几个不同的组。但是可能出现的问题就是:假设1 2属于一个团赋值为a,3 4属于一个团赋值为b,再1 3属于一个团赋值为c;

那么 1 2 3 4

c a c b,分别对应的值如前面所示,得到的结果就是3个区域,但实际上只要1个区域就够了。

(如果在遇到1 3两个亲友都已经有所属的团时,就需要把其中一个亲友以及他所属的团的所有亲友的值都改为另一个亲友的值,这样时间复杂度太高)

这种思路无法解决问题。

解题思路二

首先给数组中每个元素赋值为其下标的值。当i == a[i]表示i为该团的团长;若i != a[i]表示i属于某个团。

统计结束后经过一次遍历,若i==a[i],则区域数加1。最终得出答案。

这个思路关键在于给出的两个亲友是否已经有所属团。

如给出p,q两个亲友

若p,q都没有所属团,则a[p]=p;a[q]=a[p];

若p有q没有         ,则a[q]=a[p];

若q有p没有         ,则a[p]=a[q];

若p,q都有所属团  ,则需要找出p所属团的团长,并把团长的值改为q所属团的团长的值。

while(p != a[p])
{
  p = a[p];
}while(q != a[q]){  q = a[q];}a[p] = a[q];

完整代码:

 1 #include <cstdio>
 2 #include <iostream>
 3 #include <string.h>
 4 #include <cmath>
 5 using namespace std;
 6
 7 const int maxn=10010;
 8 int p[maxn];
 9
10 int main()
11 {
12      int t;
13     scanf("%d", &t);
14     while(t--)
15     {
16         int k, m;
17         scanf("%d%d", &k, &m);
18         memset(p, 0, sizeof(p));
19         for(int i=0; i<m; ++i)
20         {
21             int a, b;
22             scanf("%d%d", &a, &b);
23             if(p[a]==0 && p[b]==0)
24             {
25                 p[a]=a;
26                 p[b]=p[a];
27             }
28             else
29             {
30                 if(p[a]==0 && p[b]!=0)
31                 {
32                     p[a]=p[b];
33                 }
34                 else
35                 {
36                     if(p[a]!=0 && p[b]==0)
37                     {
38                         p[b]=p[a];
39                     }
40                     else
41                     {
42                         if(p[a] != p[b])
43                         {
44                              while(p[a] != a)
45                              {
46                                  a = p[a];
47                              }
48                              while(p[b] != b)
49                              {
50                                  b = p[b];
51                              }
52                              p[a] = p[b];
53                          }
54                     }
55                 }
56             }
57         }
58         int ans = 0;
59         for(int i=1; i<=k; ++i)
60         {
61             if(p[i]==i || p[i]==0)
62             {
63                 ++ans;
64             }
65         }
66         printf("%d\n", ans);
67     }
68     return 0;
69 }

解题思路三

并查集(Union-find sets):是一种简单的用途广泛的集合。并查集是若干个不相交的集合,能够较快的合并和判断元素所在集合。相关应用有求无向图的连通分量个数,最小

公共祖先,带限制的作业排序,以及Kruskar算法求最小生成树。

基本做法和思路二相似,只是加入了类似权值的数组,每次将两个团合并入权值较大的那个团。

 1 #include <stdio.h>
 2 #include <iostream>
 3 #include <string.h>
 4 #include <cmath>
 5 using namespace std;
 6
 7 const int maxn=10005;
 8 int p[maxn];
 9 int r[maxn];
10
11 int main()
12 {
13     int t;
14     scanf("%d", &t);
15     while(t--)
16     {
17         int k, m, count=0;
18         scanf("%d%d", &k, &m);
19         memset(r, 0, sizeof(r));
20         for(int i=1; i<maxn; ++i) p[i]=i;
21         for(int i=0; i<m; ++i)
22         {
23             int a, b;
24             scanf("%d%d", &a, &b);
25             while(a != p[a])
26             {
27                 a = p[a];
28             }
29             while(b != p[b])
30             {
31                 b = p[b];
32             }
33             if(a == b) continue;
34             if(r[a] > r[b])
35             {
36                 p[b] = a;
37             }
38             else
39             {
40                 p[a] = b;
41                 if(r[a] == r[b])
42                 {
43                     r[b]++;
44                 }
45             }
46         }
47         int ans = 0;
48         for(int i=1; i<=k; ++i)
49         {
50             if(p[i]==i)
51             {
52                 ++ans;
53             }
54         }
55         printf("%d\n", ans);
56     }
57     return 0;
58 }
时间: 2024-12-12 13:55:46

NOJ 1798的相关文章

NOJ 1972 炒股票的女巫璐璐 &amp;&amp; NOJ 1974 BRN (浅谈两点法)

两点法是通常用来求解简单的区间问题的O(n)算法,两点法顾名思义有两个点,一个起点一个终点,在区间上维护这两个点,动态更新题目要求的值的大小 这里举出NOJ两道较简单且有代表性的题,一道是数字一道是字符串 炒股票的女巫璐璐 时间限制(普通/Java):1000MS/3000MS         运行内存限制:65536KByte 总提交:674          测试通过:40 题目描述 女巫璐璐生活在美丽的仙林.璐璐突发奇想想到了炒股.璐璐运用了她的神奇魔法获取了一支股票未来连续N天的价格.

1798: [Ahoi2009]Seq 维护序列seq

1798: [Ahoi2009]Seq 维护序列seq Time Limit: 30 Sec  Memory Limit: 64 MBSubmit: 5886  Solved: 2087[Submit][Status][Discuss] Description 老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成. 有长为N的数列,不妨设为a1,a2,…,aN .有如下三种操作形式: (1)把数列中的一段数全部乘一个值; (2)把数列中的一段数全部加一个值; (3)询问数列中的一段数的和

bzoj 1798 维护序列seq

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1798 题解: 高级一点的线段树,加上了区间乘法运算,则需要增加一个数组mulv记录乘的因数,在下放更新sumv和addv值的都时候要先乘再加 被蓝书的写法坑了,就一直搞不懂下放和sumv.addv数组的具体用法,导致网上大犇们的程序我基本都看不懂,写完这道题感觉重新学了一遍线段树 1 #include<cstdio> 2 #include<cstring> 3 #defin

BZOJ 1798 题解

1798: [Ahoi2009]Seq 维护序列seq Time Limit: 30 Sec  Memory Limit: 64 MBSubmit: 5531  Solved: 1946[Submit][Status][Discuss] Description 老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成. 有长为N的数列,不妨设为a1,a2,…,aN .有如下三种操作形式: (1)把数列中的一段数全部乘一个值; (2)把数列中的一段数全部加一个值; (3)询问数列中的一段数的和

【BZOJ】【1798】【AHOI2009】Seq维护序列

线段树 属于线段树中级应用吧…… 要打两种标记:乘法和加法标记.一开始我想着可以像只有加法标记那样,永不下传,查询的时候依次累加就好了.后来发现不会写……只好每次update的时候……遇到标记!下传!query的时候遇到标记!下传!暴力地来搞…… 然后说下下传的细节:先传乘法,后传加法.因为传乘法标记的时候要连sum带add都要一起乘,如果先传add就会多乘…… 1 /************************************************************** 2

【BZOJ】1798: [Ahoi2009]Seq 维护序列seq(线段树)

http://www.lydsy.com/JudgeOnline/problem.php?id=1798 之前写了个快速乘..........................20多s...... 还好1a.. 那么本题就是维护两个tag即可.和上一题一样. #include <cstdio> #include <cstring> #include <cmath> #include <string> #include <iostream> #inc

NOJ 1063 生活的烦恼

描述 生活的暑假刚集训开始,他要决心学好字典树,二叉树,线段树和各种树,但生活在OJ上刷题的时候就遇到了一个特别烦恼的问题.那当然就是他最喜欢的二二叉树咯!题目是这样的:给你一颗非空的二叉树,然后再给你一个整数n,让生活输出这颗二叉树的第n(n>0且n<=树的深度)层,出题者为了给生活降低难度,要求两个输出数据之间用'~'隔开.看来我们的出题人很有爱啊! 输入 第一行输入一个数N,表示有N组测试数据.接下来N行,每行一个字符串,用'#'表示为空的节点,树的结束标志为'@'.'@'后仅有一个空格

bzoj 1798 [Ahoi2009]Seq 维护序列seq(线段树+传标)

[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1798 [题意] 给定一个序列,要求提供区间乘/加,以及区间求和的操作 [思路] 线段树+传标. 下传标记的方式可以类比这里 click here [代码] 1 #include<set> 2 #include<cmath> 3 #include<queue> 4 #include<vector> 5 #include<cstdio>

POJ 1798 Truck History

Description Advanced Cargo Movement, Ltd. uses trucks of different types. Some trucks are used for vegetable delivery, other for furniture, or for bricks. The company has its own code describing each type of a truck. The code is simply a string of ex