一介褥、并查集
?并查集迹冤,在一些有N個元素的集合應(yīng)用問題中嚣州,我們通常是在開始時讓每個元素構(gòu)成一個單元素的集合,然后按一定順序?qū)儆谕唤M的元素所在的集合合并硝拧,其間要反復(fù)查找一個元素在哪個集合中径筏。這一類問題近幾年來反復(fù)出現(xiàn)在信息學(xué)的國際國內(nèi)賽題中,其特點是看似并不復(fù)雜障陶,但數(shù)據(jù)量極大滋恬,若用正常的數(shù)據(jù)結(jié)構(gòu)來描述的話,往往在空間上過大抱究,計算機無法承受恢氯;即使在空間上勉強通過,運行的時間復(fù)雜度也極高鼓寺,根本就不可能在比賽規(guī)定的運行時間(1~3秒)內(nèi)計算出試題需要的結(jié)果勋拟,只能用并查集來描述。
?例如:若某個家族人員過于龐大妈候,要判斷兩個是否是親戚敢靡,確實還很不容易,給出某個親戚關(guān)系圖州丹,求任意給出的兩個人是否具有親戚關(guān)系醋安。 規(guī)定:x和y是親戚杂彭,y和z是親戚,那么x和z也是親戚吓揪。如果x,y是親戚,那么x的親戚都是y的親戚柠辞,y的親戚也都是x的親戚。
?算法:
(1) 開始時叭首,為每個人建立一個集合Set(x);
(2) 得到一個關(guān)系后a,b图毕,合并相應(yīng)集合Union(a,b)眷唉;
題目描述:某省調(diào)查城鎮(zhèn)交通狀況,得到現(xiàn)有城鎮(zhèn)道路統(tǒng)計表冬阳,表中列出了每條道路直接連通的城鎮(zhèn)。省政府“暢通工程”的目標(biāo)是使全省任何兩個城鎮(zhèn)間都可以實現(xiàn)交通(但不一定有直接的道路相連驳庭,只要互相間接通過道路可達即可)。問最少還需要建設(shè)多少條道路饲常?
輸入描述:
測試輸入包含若干測試用例。每個測試用例的第1行給出兩個正整數(shù)荞驴,分別是城鎮(zhèn)數(shù)目N ( < 1000 )和道路數(shù)目M不皆;隨后的M行對應(yīng)M條道路,每行給出一對正整數(shù)熊楼,分別是該條道路直接連通的兩個城鎮(zhèn)的編號霹娄。為簡單起見,城鎮(zhèn)從1到N編號鲫骗。
注意:兩個城市之間可以有多條道路相通,也就是說
3 3
1 2
1 2
2 1
這種輸入也是合法的
當(dāng)N為0時犬耻,輸入結(jié)束,該用例不被處理执泰。
輸出描述:
對每個測試用例枕磁,在1行里輸出最少還需要建設(shè)的道路數(shù)目。
示例輸入:
4 2
1 3
4 3
3 3
1 2
1 3
2 3
5 2
1 2
3 5
999 0
0
示例輸出:
1
0
2
998
#include<iostream>
#include<vector>
using namespace std;
int roads[1001];
int main(){
while (true){
int n, m;
cin >> n;
if (n == 0){
return 0;
}
cin >> m;
for (int i = 1; i <= n; i++){
roads[i] = -1;
}
for (int i = 0; i < m; i++){
int a, b;
cin >> a >> b;
int max = roads[a] > roads[b] ? roads[a] : roads[b];
if (max == -1){
roads[a] = b;
}
else{
int ai = a;
int bi = b;
while (roads[ai] != -1){
ai = roads[ai];
}
while (roads[bi] != -1){
bi = roads[bi];
}
if (ai != bi){
roads[bi] = ai;
}
}
}
int sum = 0;
for (int i = 1; i <= n; i++){
if (roads[i] == -1){
sum++;
}
}
cout << sum - 1 << endl;
}
return 0;
}
二术吝、最小生成樹
?根據(jù)Kruskal算法可以生成最小生成樹计济,即每次選取最小權(quán)重的邊茸苇,若不成環(huán),則加入集合沦寂。而檢測是否成環(huán)可以根據(jù)并查集進行計算学密。
題目描述:
?某省調(diào)查鄉(xiāng)村交通狀況,得到的統(tǒng)計表中列出了任意兩村莊間的距離传藏。省政府“暢通工程”的目標(biāo)是使全省任何兩個村莊間都可以實現(xiàn)公路交通(但不一定有直接的公路相連腻暮,只要能間接通過公路可達即可),并要求鋪設(shè)的公路總長度為最小毯侦。請計算最小的公路總長度哭靖。
輸入描述:
?測試輸入包含若干測試用例。每個測試用例的第1行給出村莊數(shù)目N ( < 100 )侈离;隨后的N(N-1)/2行對應(yīng)村莊間的距離试幽,每行給出一對正整數(shù),分別是兩個村莊的編號卦碾,以及此兩村莊間的距離蔗坯。為簡單起見宾濒,村莊從1到N編號绘梦。
?當(dāng)N為0時,輸入結(jié)束榄棵,該用例不被處理疹鳄。
輸出描述:
?對每個測試用例瘪弓,在1行里輸出最小的公路總長度腺怯。
示例輸入:
3
1 2 1
1 3 2
2 3 4
4
1 2 1
1 3 4
1 4 1
2 3 3
2 4 2
3 4 5
0
輸出:
3
5
備注:
采用<algorithm>頭文件中的sort函數(shù)虑乖,可以進行遞增排序决左。
根據(jù)需要重載 < :
bool operator < (const Template &A) const{}
#include<iostream>
#include<algorithm>
using namespace std;
int roads[101];
struct Node{
int a, b;
int cost;
bool operator < (const Node &A) const {
return cost < A.cost;
}
}node[5000];
int findroot(int a){
if (roads[a] == -1)
return a;
int i = a;
while (roads[i] != -1){
i = roads[i];
}
//roads[a] = i;
return i;
}
bool unionset(Node nd){
int aroot = findroot(nd.a);
int broot = findroot(nd.b);
if (aroot != broot){
roads[aroot] = broot;
return true;
}
else{
return false;
}
}
int main(){
while (true){
int n;
cin >> n;
if (n == 0){
return 0;
}
int times = n*(n - 1) / 2;
for (int i = 1; i <= n; i++){
roads[i] = -1;
}
for (int i = 0; i < times; i++){
cin >> node[i].a >> node[i].b >> node[i].cost;
}
sort(node,node+times);
int iter = 1;
int sum = 0;
for (int i = 0; iter < n; i++){
if (unionset(node[i])){
iter++;
sum += node[i].cost;
}
}
cout << sum << endl;
}
return 0;
}
三、最短路徑
題目描述:
?在每年的校賽里继找,所有進入決賽的同學(xué)都會獲得一件很漂亮的t-shirt婴渡。但是每當(dāng)我們的工作人員把上百件的衣服從商店運回到賽場的時候边臼,卻是非常累的柠并!所以現(xiàn)在他們想要尋找最短的從商店到賽場的路線,你可以幫助他們嗎啃沪?
輸入描述:
?輸入包括多組數(shù)據(jù)创千。每組數(shù)據(jù)第一行是兩個整數(shù)N追驴、M(N<=100氯檐,M<=10000)冠摄,N表示成都的大街上有幾個路口,標(biāo)號為1的路口是商店所在地沃呢,標(biāo)號為N的路口是賽場所在地某抓,M則表示在成都有幾條路否副。N=M=0表示輸入結(jié)束备禀。接下來M行曲尸,每行包括3個整數(shù)A另患,B柴淘,C(1<=A,B<=N,1<=C<=1000),表示在路口A與路口B之間有一條路敛熬,我們的工作人員需要C分鐘的時間走過這條路。
?輸入保證至少存在1條商店到賽場的路線话原。
輸出描述:
?對于每組輸入,輸出一行黄虱,表示工作人員從商店走到賽場的最短時間
示例輸入:
2 1
1 2 3
3 3
1 2 5
2 3 5
3 1 2
0 0
輸出:
3
2
#include<iostream>
using namespace std;
const int NOWAY = -1;
const int VNUM = 101;
// 鄰接矩陣
int cost[VNUM][VNUM];
// Floyd算法
// cost[k][i][j] = min { cost[k-1][i][j],
// cost[k-1][i][k] + cost[kNOWAY][k][j] }
// 從i到j(luò)且中間節(jié)點最大不超過k
// 第一維數(shù)組可優(yōu)化
void floyd(int n){
for (int k = 1; k <= n; k++){
for (int i = 1; i <= n; i++){
for (int j = 1; j <= n; j++){
if (cost[i][k]!=NOWAY && cost[k][j]!=NOWAY){
if (cost[i][j] == NOWAY || cost[i][j] > cost[i][k] + cost[k][j]){
cost[i][j] = cost[i][k] + cost[k][j];
}
}
}
}
}
if (cost[1][n] != NOWAY){
cout << cost[1][n] << endl;
}
}
// Dijkstra算法
// 從一個節(jié)點出發(fā)捻浦,不斷迭代最短距離昧识。
// 從length[]中選取最小長度對應(yīng)的結(jié)點J,加入新結(jié)點j跪楞。
// 更新length[]甸祭。 a[k] = min { a[j] + cost[j][k] }
// 循環(huán)N次
bool mark[VNUM]; // 記錄節(jié)點是否已加入
int length[VNUM];
void dijkstra(int n, int begin,int end){
for (int i = 1; i <= n; i++){
length[i] = -1;
mark[i] = false;
}
// 加入開始節(jié)點
mark[begin] = true;
length[begin] = 0;
int newp = begin;
// 不斷加入新節(jié)點淋叶,循環(huán)加入n次
for (int i = 1; i <= n; i++){
for (int j = 1; j <= n; j++){
// 根據(jù)新加的節(jié)點的邊煞檩,更新距離數(shù)組
if (mark[j] || cost[newp][j] == -1){
continue;
}
// 如果節(jié)點j不可達斟湃,但是newp可達凝赛;或者
// length[j]>length[newp]+cost[newp][j] 更新墓猎。
if (length[j]==-1||length[j]>length[newp]+cost[newp][j]){
length[j] = length[newp] + cost[newp][j];
}
}
// 尋找未加入的節(jié)點中最短路對應(yīng)的節(jié)點毙沾,當(dāng)作新加入的節(jié)點。
int min = 999999999;
for (int k = 1; k <= n; k++){
if (!mark[k] && length[k] != -1 && length[k] < min){
min = length[k];
newp = k;
}
}
mark[newp] = true;
}
cout << length[end] << endl;
}
int main(){
while (true){
int n, m;
cin >> n >> m;
if (n == 0 && m == 0){
return 0;
}
int a, b, c;
for (int i = 1; i <= n; i++){
for (int j = 1; j <= n; j++){
cost[i][j] = NOWAY;
}
cost[i][i] = 0;
}
for (int i = 0; i < m; i++){
cin >> a >> b >> c;
cost[a][b] = c;
cost[b][a] = c;
}
// floyd(n);
dijkstra(n, 1, n);
}
return 0;
}
四烤宙、拓?fù)渑判?/strong>
題目描述:
?有N個比賽隊(1<=N<=500),編號依次為1屯远,2慨丐,3备闲,恬砂。泻骤。。亲轨。器虾,N進行比賽兆沙,比賽結(jié)束后葛圃,裁判委員會要將所有參賽隊伍從前往后依次排名,但現(xiàn)在裁判委員會不能直接獲得每個隊的比賽成績尚氛,只知道每場比賽的結(jié)果阅嘶,即P1贏P2抡蛙,用P1粗截,P2表示,排名時P1在P2之前∈幔現(xiàn)在請你編程序確定排名昂利。
輸入描述:
?輸入有若干組,每組中的第一行為二個數(shù)N(1<=N<=500)窝撵,M;其中N表示隊伍的個數(shù)襟铭,M表示接著有M行的輸入數(shù)據(jù)碌奉。接下來的M行數(shù)據(jù)中,每行也有兩個整數(shù)P1寒砖,P2表示即P1隊贏了P2隊赐劣。
輸出描述:
?給出一個符合要求的排名。輸出時隊伍號之間有空格哩都,最后一名后面沒有空格魁兼。
其他說明:
?符合條件的排名可能不是唯一的,此時要求輸出時編號小的隊伍在前漠嵌;輸入數(shù)據(jù)保證是正確的,即輸入數(shù)據(jù)確保一定能有一個符合要求的排名蟹瘾。
示例輸入:
4 3
1 2
2 3
4 3
輸出:
1 2 4 3
// 不斷尋找入度為0的頂點。
#include<iostream>
using namespace std;
const int VNUM = 501;
int cost[VNUM][VNUM];
int in[VNUM];
int ans[VNUM];
void topo(int n){
int index = 1;
for (int k = 0; k < n; k++){
for (int i = 1; i <= n; i++){
if (in[i] == 0){
ans[index] = i;
for (int j = 1; j <= n; j++){
if (cost[i][j] == 1){
in[j]--;
}
}
in[i] = -1;
index++;
break;
}
}
}
for (int i = 1; i < n; i++){
cout << ans[i] << " ";
}
cout <<ans[n]<< endl;
}
int main(){
int n, m;
while (cin >> n >> m){
int a, b;
for (int i = 1; i <= n; i++){
for (int j = 1; j <= n; j++){
cost[i][j] = 0;
}
in[i] = 0;
}
for (int i = 0; i < m; i++){
cin >> a >> b;
// 防止重邊
if (cost[a][b] == 0)
in[b]++;
cost[a][b] = 1;
}
topo(n);
}
return 0;
}
五、廣度優(yōu)先搜索和深度優(yōu)先搜索
問題描述:(勝利大逃亡)
?Ignatius被魔王抓走了,有一天魔王出差去了,這可是Ignatius逃亡的好機會.
?魔王住在一個城堡里,城堡是一個ABC的立方體,可以被表示成A個B*C的矩陣,剛開始Ignatius被關(guān)在(0,0,0)的位置,離開城堡的門在(A-1,B-1,C-1)的位置,現(xiàn)在知道魔王將在T分鐘后回到城堡,Ignatius每分鐘能從一個坐標(biāo)走到相鄰的六個坐標(biāo)中的其中一個.現(xiàn)在給你城堡的地圖,請你計算出Ignatius能否在魔王回來前離開城堡(只要走到出口就算離開城堡,如果走到出口的時候魔王剛好回來也算逃亡成功),如果可以請輸出需要多少分鐘才能離開,如果不能則輸出-1.
輸入:
?輸入數(shù)據(jù)的第一行是一個正整數(shù)K,表明測試數(shù)據(jù)的數(shù)量.每組測試數(shù)據(jù)的第一行是四個正整數(shù)A,B,C和T(1<=A,B,C<=50,1<=T<=1000),它們分別代表城堡的大小和魔王回來的時間.然后是A塊輸入數(shù)據(jù)(先是第0塊,然后是第1塊,第2塊......),每塊輸入數(shù)據(jù)有B行,每行有C個正整數(shù),代表迷宮的布局,其中0代表路,1代表墻.(如果對輸入描述不清楚,可以參考Sample Input中的迷宮描述,它表示的就是上圖中的迷宮)
特別注意:本題的測試數(shù)據(jù)非常大,請使用scanf輸入,我不能保證使用cin能不超時.在本OJ上請使用Visual C++提交.輸出:
?對于每組測試數(shù)據(jù),如果Ignatius能夠在魔王回來前離開城堡,那么請輸出他最少需要多少分鐘,否則輸出-1.Sample Input
1
3 3 4 20
0 1 1 1
0 0 1 1
0 1 1 1
1 1 1 1
1 0 0 1
0 1 1 1
0 0 0 0
0 1 1 0
0 1 1 0Sample Output
11
解題思路:DFS芦圾,每次向六個方向擴展,時間加1,采用queue數(shù)據(jù)結(jié)構(gòu),第一次遍歷到出口 時即最短時間弃酌。
問題:第一段是網(wǎng)上答案乌询,第二段是我的代碼鬼佣。我的代碼一直出現(xiàn)內(nèi)存超限税迷,暫未找到原因。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <iostream>
#include <algorithm>
#include <math.h>
#include <queue>
using namespace std;
bool mark[50][50][50];
bool m[50][50][50];
int t;
int go[][3] =
{
1, 0, 0,
-1, 0, 0,
0, 1, 0,
0, -1, 0,
0, 0, 1,
0, 0, -1
};
struct e
{
int x, y, z;
int t;
};
queue <e> q;
int bfs(int a, int b, int c)
{
while (!q.empty())
{
e now;
now = q.front();
q.pop();
if (now.t >= t) {
return -1;
}
for (int i = 0; i<6; i++)
{
int nx = now.x + go[i][0];
int ny = now.y + go[i][1];
int nz = now.z + go[i][2];
if (nx<0 || nx >= a || ny<0 || ny >= b || nz<0 || nz >= c) continue;
if (mark[nx][ny][nz] == 1) continue;
if (m[nx][ny][nz] == 1) continue;
m[nx][ny][nz] = 1;
e tmp;
tmp.x = nx;
tmp.y = ny;
tmp.z = nz;
tmp.t = now.t + 1;
if (tmp.t <t) {
q.push(tmp);
}
mark[nx][ny][nz] == 1;
if (nx == a - 1 && ny == b - 1 && nz == c - 1)
return tmp.t;
}
}
return -1;
}
int main()
{
int a, b, c;
int k;
while (scanf_s("%d", &k) != eof)
{
while (k--)
{
scanf_s("%d%d%d%d", &a, &b, &c, &t);
for (int i = 0; i<a; i++)
{
for (int j = 0; j<b; j++)
{
for (int k = 0; k<c; k++)
{
scanf_s("%d", &m[i][j][k]);
mark[i][j][k] = 0;
}
}
}
while (!q.empty()) q.pop();
e tmp;
tmp.x = tmp.y = tmp.z = tmp.t = 0;
q.push(tmp);
mark[0][0][0] = 1;
int rec = bfs(a, b, c);
if (rec <= t && rec != -1) printf("%d\n", rec);
else printf("-1\n");
}
}
return 0;
}
#include<iostream>
#include<queue>
using namespace std;
const int MAX = 50;
int map[MAX][MAX][MAX];
bool mark[MAX][MAX][MAX];
int A, B, C, T;
struct Dot{
int a;
int b;
int c;
int t;
};
queue<Dot> Q;
void BFS(){
while (Q.empty() == false){
Dot dot = Q.front();
int a = dot.a;
int b = dot.b;
int c = dot.c;
int t = dot.t;
if (a == A - 1 && b == B - 1 && c == C - 1 && !mark[a][b][c]){
mark[a][b][c] = true;
if (t <= T)
cout << t << endl;
else
cout << -1 << endl;
return;
}
if (t > T){
cout << -1 << endl;
return;
}
mark[a][b][c] = true;
Dot temp;
if (a > 0 && !mark[a - 1][b][c] && map[a - 1][b][c] == 0){
temp.t = t + 1;
temp.a = a - 1;
temp.b = b;
temp.c = c;
Q.push(temp);
}
if (a<A-1&&!mark[a + 1][b][c] && map[a + 1][b][c] == 0){
temp.t = t + 1;
temp.a = a + 1;
temp.b = b;
temp.c = c;
Q.push(temp);
}
if (b > 0 && !mark[a][b - 1][c] && map[a][b - 1][c] == 0){
temp.t = t + 1;
temp.a = a;
temp.b = b-1;
temp.c = c;
Q.push(temp);
}
if (b<B-1&&map[a][b + 1][c] == 0 && !mark[a][b + 1][c]){
temp.t = t + 1;
temp.a = a ;
temp.b = b+1;
temp.c = c;
Q.push(temp);
}
if (c > 0 && !mark[a][b][c - 1] && map[a][b][c - 1] == 0){
temp.t = t + 1;
temp.a = a;
temp.b = b;
temp.c = c-1;
Q.push(temp);
}
if (c<C-1&&map[a][b][c + 1] == 0 && !mark[a][b][c + 1]){
temp.t = t + 1;
temp.a = a;
temp.b = b;
temp.c = c + 1;
Q.push(temp);
}
Q.pop();
}
}
int main(){
int n;
while (scanf_s("%d", &n) != EOF){
scanf_s("%d%d%d%d", &A, &B, &C, &T);
for (int i = 0; i < A; i++)
{
for (int j = 0; j < B; j++)
{
for (int k = 0; k < C; k++)
{
map[i][j][k] = 0;
// cin >> map[i][j][k];
scanf_s("%d", &map[i][j][k]);
mark[i][j][k] = 0;
}
}
}
while (!Q.empty()){
Q.pop();
}
Dot d;
d.a = 0;
d.b = 0;
d.c = 0;
d.t = 0;
map[0][0][0] = 0;
Q.push(d);
BFS();
if (!mark[A - 1][B - 1][C - 1])
cout << -1 << endl;
}
return 0;
}
Problem Description(非吃钐澹可樂)
?大家一定覺的運動以后喝可樂是一件很愜意的事情,但是seeyou卻不這么認(rèn)為。因為每次當(dāng)seeyou買了可樂以后疲吸,阿牛就要求和seeyou一起分享這一瓶可樂,而且一定要喝的和seeyou一樣多孵运。但seeyou的手中只有兩個杯子,它們的容量分別是N 毫升和M 毫升 可樂的體積為S (S<101)毫升 (正好裝滿一瓶) 咐熙,它們?nèi)齻€之間可以相互倒可樂 (都是沒有刻度的去团,且 S==N+M鞋吉,101>S>0,N>0改抡,M>0) 欠拾。聰明的ACMER你們說他們能平分嗎荆忍?如果能請輸出倒可樂的最少的次數(shù),如果不能輸出"NO"岳守。Input
?三個整數(shù) : S 可樂的體積 , N 和 M是兩個杯子的容量佣赖,以"0 0 0"結(jié)束。Output
?如果能平分的話請輸出最少要倒的次數(shù)记盒,否則輸出"NO"憎蛤。Sample Input
7 4 3
4 1 3
0 0 0Sample Output
NO
3
#include<iostream>
#include<queue>
using namespace std;
struct Liquid{
int n;
int m;
int s;
int times;
};
bool mark[101][101][101];
queue<Liquid> Q;
int N, M, S;
void AtoB(int &a, int &b, int max){
if (a + b > max){
a = a + b - max;
b = max;
}
else{
b = a + b;
a = 0;
}
}
void push_back(int n, int m, int s, int t){
if (n > N || m > M || s > S){
return;
}
Liquid next ;
if (mark[n][m][s] == false){
next.n = n;
next.m = m;
next.s = s;
next.times = t + 1;
Q.push(next);
}
}
int main(){
while (true){
cin >> S >> N >> M;
bool flag = false;
if (S == 0 && N == 0 && M == 0){
return 0;
}
if (S % 2 != 0){
cout << "NO" << endl;
continue;
}
for (int i = 0; i <= N; i++){
for (int j = 0; j <= M; j++){
for (int k = 0; k <= S; k++)
mark[i][j][k] = 0;
}
}
while (!Q.empty()){
Q.pop();
}
Liquid temp;
temp.n = 0;
temp.m = 0;
temp.s = S;
temp.times = 0;
Q.push(temp);
while (Q.empty() == false){
Liquid now = Q.front();
Liquid next;
Q.pop();
int n, m, s, t;
n = now.n;
m = now.m;
s = now.s;
t = now.times;
mark[n][m][s] = true;
if ((n == S / 2 && m == S / 2) || (n == S / 2 && s == S / 2) || (m == S / 2 && s == S / 2)){
cout << t << endl;
flag = true;
break;
}
AtoB(n, m, M);
push_back(n, m, s, t);
n = now.n;
m = now.m;
s = now.s;
t = now.times;
AtoB(n, s, S);
push_back(n, m, s, t);
n = now.n;
m = now.m;
s = now.s;
t = now.times;
AtoB(m, n, N);
push_back(n, m, s, t);
n = now.n;
m = now.m;
s = now.s;
t = now.times;
AtoB(m, s, S);
push_back(n, m, s, t);
n = now.n;
m = now.m;
s = now.s;
t = now.times;
AtoB(s, m, M);
push_back(n, m, s, t);
n = now.n;
m = now.m;
s = now.s;
t = now.times;
AtoB(s, n, N);
push_back(n, m, s, t);
}
if (flag)
continue;
cout << "NO" << endl;
}
return 0;
}
此類題目應(yīng)先確認(rèn)狀態(tài)的轉(zhuǎn)移關(guān)系。
(x,y,z...) ---> (xx,yy,zz...).。
其次找清判斷結(jié)果的條件俩檬。
六萎胰、A*算法(拓展)
綜合了DFS/BFS和啟發(fā)式尋路算法。