藍橋杯備賽(五) 雙指針,BFS與圖論
一、雙指針
1.Acwing 1238. 日誌統計
小明維護着一個程序員論壇。現在他收集了一份”點贊”日誌,日誌共有 N 行。
其中每一行的格式是:
ts id
表示在 ts 時刻編號 id 的帖子收到一個”贊”。
現在小明想統計有哪些帖子曾經是”熱帖”。
如果一個帖子曾在任意一個長度爲 D 的時間段內收到不少於 K 個贊,小明就認爲這個帖子曾是”熱帖”。
具體來說,如果存在某個時刻 TT 滿足該帖在 [T,T+D)這段時間內(注意是左閉右開區間)收到不少於 K 個贊,該帖就曾是”熱帖”。
給定日誌,請你幫助小明統計出所有曾是”熱帖”的帖子編號。
輸入格式
第一行包含三個整數 N,D,K。
以下 N 行每行一條日誌,包含兩個整數 ts和 id。
輸出格式
按從小到大的順序輸出熱帖 id。
每個 id 佔一行。
數據範圍
1≤K≤N≤105
0≤ts,id≤105
1≤D≤10000
輸入樣例:
7 10 2
0 1
0 10
10 10
10 1
9 1
100 3
100 3
輸出樣例:
1
3
暴力做法(會超時)
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=100010;
pair <int,int> logs[N];
int cnt[N]; //計數
bool st[N];
int n,d,k;
int main()
{
scanf("%d%d%d",&n,&d,&k);
for(int i=0;i<n;i++) scanf("%d%d",&logs[i].first,&logs[i].second);
sort(logs,logs+n);
for(int i=0;i<n;i++) //i一個個循環,j在時長小於d的範圍內移動
{
memset(cnt,0,sizeof(cnt));
int m=0; //用來計數
for(int j=i;;j++) //時長超過d停止
{
m=logs[j].first-logs[i].first;
if(m>=d||m<0) break;
int id=logs[j].second;
cnt[id]++;
if(cnt[id]>=k)
st[id]=true;
}
}
for (int i = 0; i < N; i++)
if (st[i])
printf("%d\n", i);
getchar();getchar();
return 0;
}
利用雙指針算法對暴力算法優化,雙指針算法即有i,j兩個指針,根據題意對原本的兩個for優化。本題中,對於每次j在d範圍內的循環,其實只是在上一次的基礎上前面加一個,後面減一個,不用從頭開始循環。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=100010;
pair <int,int> logs[N];
int cnt[N]; //計數
bool st[N];
int n,d,k;
int main()
{
scanf("%d%d%d",&n,&d,&k);
for(int i=0;i<n;i++) scanf("%d%d",&logs[i].first,&logs[i].second);
sort(logs,logs+n);
for(int i=0,j=0;i<n;i++) //這裏是i先移動,j停留在原地
{
int id=logs[i].second;
cnt[id]++;
while(logs[i].first-logs[j].first>=d)
{
cnt[logs[j].second]--;
j++; //當超過指定時間範圍d後,j再移動
}
if(cnt[id]>=k)
st[id]=true;
}
for (int i = 0; i < N; i++)
if (st[i])
printf("%d\n", i);
getchar();getchar();
return 0;
}
二、BFS
隊列----先進先出-----BFS—寬搜
棧-------先進後出-----DFS—深搜
BFS較DFS的特點是,可找到步數最小的合法路徑
BFS原則:每次取出隊頭元素,將拓展出的所有元素放到隊尾
僞代碼:
queue <-初始狀態 //起點入隊
while(queue非空)
{
t=隊頭元素
for(拓展t節點)
{
ver =新節點
if(!st[ver]) //判重
{
ver ->隊尾 //ver入隊,放在隊尾
}
}
}
1.Acwing 1101. 獻給阿爾吉儂的花束
阿爾吉儂是一隻聰明又慵懶的小白鼠,它最擅長的就是走各種各樣的迷宮。
今天它要挑戰一個非常大的迷宮,研究員們爲了鼓勵阿爾吉儂儘快到達終點,就在終點放了一塊阿爾吉儂最喜歡的奶酪。
現在研究員們想知道,如果阿爾吉儂足夠聰明,它最少需要多少時間就能喫到奶酪。
迷宮用一個 R×C的字符矩陣來表示。
字符 S 表示阿爾吉儂所在的位置,字符 E 表示奶酪所在的位置,字符 # 表示牆壁,字符 . 表示可以通行。
阿爾吉儂在 1 個單位時間內可以從當前的位置走到它上下左右四個方向上的任意一個位置,但不能走出地圖邊界。
輸入格式
第一行是一個正整數 T,表示一共有 T 組數據。
每一組數據的第一行包含了兩個用空格分開的正整數 R 和 C,表示地圖是一個 R×C的矩陣。
接下來的 R 行描述了地圖的具體內容,每一行包含了 C 個字符。字符含義如題目描述中所述。保證有且僅有一個 S 和 E。
輸出格式
對於每一組數據,輸出阿爾吉儂喫到奶酪的最少單位時間。
若阿爾吉儂無法喫到奶酪,則輸出“oop!”(只輸出引號裏面的內容,不輸出引號)。
每組數據的輸出結果佔一行。
數據範圍
1<T≤10
2≤R,C≤200
輸入樣例:
3
3 4
.S…
###.
…E.
3 4
.S…
.E…
…
3 4
.S…
…E.
輸出樣例:
5
1
oop!
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include<queue>
using namespace std;
const int N=210;
int n,m;
char g[N][N];
int dist[N][N]; //同時具有判重作用與記錄作用
int bfs(pair<int,int> start,pair<int,int> end)
{
queue<pair<int,int>> q; //隊列q是二元的,有兩個元素,即橫縱座標
memset(dist,-1,sizeof(dist));
dist[start.first][start.second]=0;
q.push(start); //起點入隊
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1}; //偏移量,即上下左右移動的dx,dy
while(q.size())
{
pair<int,int> t=q.front(); //取出隊頭元素
//彈出隊頭
q.pop();
for(int i=0;i<4;i++)
{
int x=t.first+dx[i],y=t.second+dy[i];
//約束條件
if(x<0||x>=n||y<0||y>=m) continue; //出界
if(dist[x][y]!=-1) continue; //走過
if(g[x][y]=='#') continue; //撞到障礙
dist[x][y]=dist[t.first][t.second]+1; //步數加一
if(end==make_pair(x,y)) return dist[x][y]; //走到終點,返回距離
//make_pair生成一個pair
q.push({x,y}); //符合要求,插入隊尾
}
}
return -1; //若沒到找到路徑,返回-1
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++) scanf("%s",g[i]); //讀入
pair<int,int> start,end;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
{
if(g[i][j]=='S') start={i,j}; //起點
else if(g[i][j]=='E') end={i,j}; //終點
}
int distance=bfs(start,end); //bfs函數輸入的參數爲起點與終點
if(distance==-1) puts("oop!");
else printf("%d\n",distance);
}
getchar();getchar();
return 0;
}
2.Acwing 1096. 地牢大師
你現在被困在一個三維地牢中,需要找到最快脫離的出路!
地牢由若干個單位立方體組成,其中部分不含岩石障礙可以直接通過,部分包含岩石障礙無法通過。
向北,向南,向東,向西,向上或向下移動一個單元距離均需要一分鐘。
你不能沿對角線移動,迷宮邊界都是堅硬的岩石,你不能走出邊界範圍。
請問,你有可能逃脫嗎?
如果可以,需要多長時間?
輸入格式
輸入包含多組測試數據。
每組數據第一行包含三個整數 L,R,C 分別表示地牢層數,以及每一層地牢的行數和列數。
接下來是 L個 R 行 C列的字符矩陣,用來表示每一層地牢的具體狀況。
每個字符用來描述一個地牢單元的具體狀況。
其中, 充滿岩石障礙的單元格用”#”表示,不含障礙的空單元格用”.”表示,你的起始位置用”S”表示,終點用”E”表示。
每一個字符矩陣後面都會包含一個空行。
當輸入一行爲”0 0 0”時,表示輸入終止。
輸出格式
每組數據輸出一個結果,每個結果佔一行。
如果能夠逃脫地牢,則輸出”Escaped in x minute(s).”,其中X爲逃脫所需最短時間。
如果不能逃脫地牢,則輸出”Trapped!”。
數據範圍
1≤L,R,C≤1001≤L,R,C≤100
輸入樣例:
3 4 5
S…
.###.
.##…
###.#
##.##
##…
#.###
####E
1 3 3
S##
#E#
0 0 0
輸出樣例:
Escaped in 11 minute(s).
Trapped!
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N=110;
int l,r,c;
char g[N][N][N];
int dist[N][N][N];
bool visited[N][N][N];
int dx[6] = {1, -1, 0, 0, 0, 0};
int dy[6] = {0, 0, 1, -1, 0, 0};
int dz[6] = {0, 0, 0, 0, 1, -1};
struct point //因爲是三維的,所以用結構體
{
int x,y,z;
};
int bfs(point start,point end)
{
queue<point> q;
memset(dist,-1,sizeof(dist));
memset(visited,0,sizeof(visited));
dist[start.x][start.y][start.z]=0;
q.push(start); //起點入隊
while(!q.empty())
{
point t=q.front(); //取出隊頭元素
q.pop(); //彈出隊頭
for(int i=0;i<6;i++)
{
int tx=t.x+dx[i];
int ty=t.y+dy[i];
int tz=t.z+dz[i];
if(tx<0||tx>=l||ty<0||ty>=r||tz<0||tz>=c) continue; //邊界
if(visited[tx][ty][tz]) continue; //走過
if(g[tx][ty][tz]=='#') continue; //障礙
//更新
dist[tx][ty][tz]=dist[t.x][t.y][t.z]+1;
visited[tx][ty][tz]=true;
if(end.x==tx&&end.y==ty&&end.z==tz) return dist[tx][ty][tz];
q.push({tx,ty,tz}); //符合要求,插入隊尾
}
}
return -1; //若沒到找到路徑,返回-1
}
int main()
{
while(cin>>l>>r>>c,l||r||c)
{
//輸入
for(int i=0;i<l;i++)
for(int j=0;j<r;j++)
scanf("%s",g[i][j]);
point start,end;
for(int i=0;i<l;i++)
for(int j=0;j<r;j++)
for(int k=0;k<c;k++)
{
char c=g[i][j][k];
if(c=='S') start={i,j,k};
else if(c=='E') end={i,j,k};
}
int distance=bfs(start,end);
if (distance == -1) puts("Trapped!");
else printf("Escaped in %d minute(s).\n", distance);
}
getchar();getchar();
return 0;
}
三、DFS
本題爲flood fill中的DFS解法
1.Acwing 1113. 紅與黑
有一間長方形的房子,地上鋪了紅色、黑色兩種顏色的正方形瓷磚。
你站在其中一塊黑色的瓷磚上,只能向相鄰(上下左右四個方向)的黑色瓷磚移動。
請寫一個程序,計算你總共能夠到達多少塊黑色的瓷磚。
輸入格式
輸入包括多個數據集合。
每個數據集合的第一行是兩個整數 W 和 H,分別表示 x 方向和 y方向瓷磚的數量。
在接下來的 H 行中,每行包括 W 個字符。每個字符表示一塊瓷磚的顏色,規則如下
1)‘.’:黑色的瓷磚;
2)‘#’:白色的瓷磚;
3)‘@’:黑色的瓷磚,並且你站在這塊瓷磚上。該字符在每個數據集合中唯一出現一次。
當在一行中讀入的是兩個零時,表示輸入結束。
輸出格式
對每個數據集合,分別輸出一行,顯示你從初始位置出發能到達的瓷磚數(記數時包括初始位置的瓷磚)。
數據範圍
1≤W,H≤201≤W,H≤20
輸入樣例:
6 9
…#.
…#
…
…
…
…
…
#@…#
.#…#.
0 0
輸出樣例:
45
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=25;
int n,m;
char g[N][N];
bool st[N][N];
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1}; //四個方向
int dfs(int x,int y)
{
int cnt=1;
st[x][y]=true;
for(int i=0;i<4;i++)
{
int a=x+dx[i],b=y+dy[i];
if(a<0||a>=n||b<0||b>=m) continue;
if(g[a][b]!='.') continue;
if(st[a][b]) continue;
cnt+=dfs(a,b);
}
return cnt;
}
int main()
{
while(cin>>m>>n)
{
if(m==0||n==0) break;
int x,y;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
{
cin>>g[i][j];
if(g[i][j]=='@')
{
x=i;
y=j;
}
}
memset(st,0,sizeof(st)); //清空上一次的狀態
cout<<dfs(x,y)<<endl;
}
getchar();getchar();
return 0;
}
2.Acwing 1233. 全球變暖
你有一張某海域 N×N像素的照片,”.”表示海洋、”#”表示陸地,如下所示:
…
.##…
.##…
…##.
…####.
…###.
…
其中”上下左右”四個方向上連在一起的一片陸地組成一座島嶼,例如上圖就有 2座島嶼。
由於全球變暖導致了海面上升,科學家預測未來幾十年,島嶼邊緣一個像素的範圍會被海水淹沒。
具體來說如果一塊陸地像素與海洋相鄰(上下左右四個相鄰像素中有海洋),它就會被淹沒。
例如上圖中的海域未來會變成如下樣子:
…
…
…
…
…#…
…
…
請你計算:依照科學家的預測,照片中有多少島嶼會被完全淹沒。
輸入格式
第一行包含一個整數N。
以下 NN 行 NN 列,包含一個由字符”#”和”.”構成的 N×N 字符矩陣,代表一張海域照片,”#”表示陸地,”.”表示海洋。
照片保證第 11 行、第 11 列、第 NN 行、第 NN 列的像素都是海洋。
輸出格式
一個整數表示答案。
數據範圍
1≤N≤1000
輸入樣例1:
7
…
.##…
.##…
…##.
…####.
…###.
…
輸出樣例1:
1
輸入樣例2:
9
…
.##.##…
.#####…
.##.##…
…
.##.#…
.#.###…
.#…#…
…
輸出樣例2:
1
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N=1010;
int n;
char g[N][N];
bool st[N][N];
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, 1, 0, -1};
void bfs(int xx,int yy,int &sum,int &bound)
{
st[xx][yy]=true;
queue<pair<int,int>> q;
q.push({xx,yy});
while(!q.empty()) //當棧爲空時才結束循環
{
pair<int,int> t=q.front();
q.pop();
sum++;
bool is_bound=false;
for(int i=0;i<4;i++)
{
int x=t.first+dx[i],y=t.second+dy[i];
if(x<0||x>=n||y<0||y>=n) continue;
if(st[x][y]) continue;
if(g[x][y]=='.')
{
is_bound=true;
continue;
}
st[x][y]=true;
q.push({x,y});
}
if(is_bound) bound++;
}
}
int main()
{
cin>>n;
for(int i=0;i<n;i++) scanf("%s",g[i]);
int cnt=0;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
if(g[i][j]=='#'&&!st[i][j]) //當遍歷到土地且未走過時
{
int sum=0,bound=0;
bfs(i,j,sum,bound);
if(sum==bound) cnt++; //沉沒量與土地量相等,證明該島嶼全部沉沒
}
cout<<cnt;
getchar();getchar();
return 0;
}
3.DFS+樹的直徑
Acwing 1207. 大臣的旅費
很久以前,T王國空前繁榮。
爲了更好地管理國家,王國修建了大量的快速路,用於連接首都和王國內的各大城市。
爲節省經費,T國的大臣們經過思考,制定了一套優秀的修建方案,使得任何一個大城市都能從首都直接或者通過其他大城市間接到達。
同時,如果不重複經過大城市,從首都到達每個大城市的方案都是唯一的。
J是T國重要大臣,他巡查於各大城市之間,體察民情。
所以,從一個城市馬不停蹄地到另一個城市成了J最常做的事情。
他有一個錢袋,用於存放往來城市間的路費。
聰明的J發現,如果不在某個城市停下來修整,在連續行進過程中,他所花的路費與他已走過的距離有關,在走第x千米到第x+1千米這一千米中(x是整數),他花費的路費是x+10這麼多。也就是說走1千米花費11,走2千米要花費23。
J大臣想知道:他從某一個城市出發,中間不休息,到達另一個城市,所有可能花費的路費中最多是多少呢?
輸入格式
輸入的第一行包含一個整數 n,表示包括首都在內的T王國的城市數。
城市從 1 開始依次編號,1 號城市爲首都。
接下來 n−1n−1 行,描述T國的高速路(T國的高速路一定是 n−1n−1 條)。
每行三個整數 Pi,Qi,Di,表示城市 Pi和城市 Qi之間有一條雙向高速路,長度爲 Di千米。
輸出格式
輸出一個整數,表示大臣J最多花費的路費是多少。
數據範圍
1≤n≤105,
1≤Pi,Qi≤n,
1≤Di≤1000
輸入樣例:
5
1 2 2
1 3 1
2 4 5
2 5 4
輸出樣例:
135
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N=100010;
int n;
struct edge
{
int id,w; //存放編號與權值
};
vector<edge> h[N];
//vector存放與i節點相連的節點,每個單元包括與之相連的的序號與權值
int dist[N];
//father表示父結點的編號,u表示當前結點的編號
void dfs(int u,int father,int distance)
{
dist[u]=distance;
for(int i=0;i<h[u].size();i++)
{
int j = h[u][i].id ;
if(j != father)
dfs(j,u,distance+h[u][i].w) ;
}
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n-1;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
h[a].push_back({b,c}); //鄰接表存儲圖
h[b].push_back({a,c});
}
dfs(1,-1,0); //dfs函數用來求當前節點到所有節點的距離
int u=1;
for(int i=1;i<=n;i++)
if(dist[i]>dist[u])
u=i;
dfs(u,-1,0);
for(int i=0;i<=n;i++)
if(dist[i]>dist[u])
u=i;
int s=dist[u];
printf("%lld\n",s*10+s*(s+1ll)/2); //這裏的1ll表示1是longlong型的
getchar();getchar();
return 0;
}
四、圖論,置換圈
圖論,置換圈
Acwing 1224. 交換瓶子
有 N個瓶子,編號 1∼N,放在架子上。
比如有 55 個瓶子:
2 1 3 5 4
要求每次拿起 22 個瓶子,交換它們的位置。
經過若干次後,使得瓶子的序號爲:
1 2 3 4 5
對於這麼簡單的情況,顯然,至少需要交換 22 次就可以復位。
如果瓶子更多呢?你可以通過編程來解決。
輸入格式
第一行包含一個整數 N,表示瓶子數量。
第二行包含 N 個整數,表示瓶子目前的排列狀況。
輸出格式
輸出一個正整數,表示至少交換多少次,才能完成排序。
數據範圍
1≤N≤10000,
輸入樣例1:
5
3 1 2 5 4
輸出樣例1:
3
輸入樣例2:
5
5 4 3 2 1
輸出樣例2:
2
#include <iostream>
#include<cstdio>
#include<string>
#include<algorithm>
using namespace std;
const int N=10010;
int a[N];
int n,k; //k表示圈數
bool st[N]; //用於判重
int main()
{
cin>>n;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
{
if(!st[i]) //統計圈數
k++;
for(int j=i;!st[j];j=a[j]) //對於一個圈內的元素全部變爲true
st[j]=true;
}
cout<<n-k;
getchar();getchar();
return 0;
}
Acwing 1240. 完全二叉樹的權值
給定一棵包含 N 個節點的完全二叉樹,樹上每個節點都有一個權值,按從上到下、從左到右的順序依次是 A1,A2,⋅⋅⋅AN,如下圖所示:
現在小明要把相同深度的節點的權值加在一起,他想知道哪個深度的節點權值之和最大?
如果有多個深度的權值和同爲最大,請你輸出其中最小的深度。
注:根的深度是 1。
輸入格式
第一行包含一個整數 N。
第二行包含 N 個整數 A1,A2,⋅⋅⋅AN。
輸出格式
輸出一個整數代表答案。
數據範圍
1≤N≤105,
−105≤Ai≤105
輸入樣例:
7
1 6 5 4 3 2 1
輸出樣例:
2
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100000;
int n;
int a[N];
int main()
{
cin>>n;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int d=0; //表示深度
long long smax=-1e18;
int depth=0;
for(int i=1;i<=n;i=i*2)
{
d++;
long long s=0;
for(int j=i;j<1<<d&&j<=n;j++) //這裏注意最後一層可能不滿,所以需要j<=n
s=s+a[j];
if(s>smax)
{
smax=s;
depth=d;
}
}
printf("%d\n",depth);
getchar();getchar();
return 0;
}