雙指標,BFS與圖論(一)
(一)雙指標
1.日誌統計
小明維護著一個程式設計師論壇。現在他收集了一份”點贊”日誌,日誌共有 N 行。
其中每一行的格式是:
ts id
表示在 ts 時刻編號 id 的帖子收到一個”贊”。
現在小明想統計有哪些帖子曾經是”熱帖”。
如果一個帖子曾在任意一個長度為 D 的時間段內收到不少於 K 個贊,小明就認為這個帖子曾是”熱帖”。
具體來說,如果存在某個時刻 T 滿足該帖在 [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
解題思路:排序+雙指標
①對所有的贊按時間排序
②通過雙指標i,j維護長度不大於d的區間,並記錄該帖子中的獲贊數
#include<iostream> #include<algorithm> #include<cstdio> #define x first #define y second using namespace std; typedef pair<int,int> PII; const int N=100010; int n,d,k; int cnt[N]; bool ts[N]; PII flags[N]; int main() { int i,j; scanf("%d%d%d",&n,&d,&k); for(i=0;i<n;i++) scanf("%d%d",&flags[i].x,&flags[i].y); sort(flags,flags+n); for(i=0,j=0;i<n;i++) { int id=flags[i].y; cnt[id]++; while(flags[i].x-flags[j].x>=d) { cnt[flags[j].y]--; j++; } if(cnt[id]>=k) ts[id]=true; } for(i=0;i<=100000;i++) { if(ts[i]) printf("%d\n",i); } return 0; }
(二)BFS
1.獻給阿爾吉儂的花束
阿爾吉儂是一隻聰明又慵懶的小白鼠,它最擅長的就是走各種各樣的迷宮。
今天它要挑戰一個非常大的迷宮,研究員們為了鼓勵阿爾吉儂儘快到達終點,就在終點放了一塊阿爾吉儂最喜歡的乳酪。
現在研究員們想知道,如果阿爾吉儂足夠聰明,它最少需要多少時間就能吃到乳酪。
迷宮用一個 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!
解題思路:要求最短的距離,應該用BFS來進行求解,我們需要map陣列來儲存地圖,同時定義一個pair陣列來儲存當前或者下一步的位置,
在定義一個vis陣列來記錄走的步數及是否走過該條路,每進行一次bfs都要對vis陣列進行初始化。
程式碼:
#include<iostream> #include<algorithm> #include<queue> #include<cstring> #define x first #define y second using namespace std; typedef pair<int,int> PII; const int N=210; int x[]={1,-1,0,0}; int y[]={0,0,1,-1}; int t,r,c; char map[N][N]; int vis[N][N]; bool check(int X,int Y) { if(X<0||X>=r||Y<0||Y>=c) return false; if(map[X][Y]=='#') return false; if(vis[X][Y]!=0) return false; return true; } int bfs(int bx,int by) { queue<PII> q; memset(vis,0,sizeof(vis)); vis[bx][by]=0; PII m; m.x=bx,m.y=by; q.push(m); while(q.size()) { PII tem=q.front(); if(map[tem.x][tem.y]=='E') return vis[tem.x][tem.y]; q.pop(); for(int i=0;i<4;i++) { int X=tem.x+x[i]; int Y=tem.y+y[i]; if(check(X,Y)==false) continue; vis[X][Y]=vis[tem.x][tem.y]+1; PII tem2; tem2.x=X,tem2.y=Y; q.push(tem2); } } return 0; } int main() { int i,j,bx,by; cin>>t; while(t--) { cin>>r>>c; for(i=0;i<r;i++) { for(j=0;j<c;j++) { cin>>map[i][j]; if(map[i][j]=='S') { bx=i; by=j; } } } int k=bfs(bx,by); if(k) { cout<<k<<endl; } else cout<<"oop!"<<endl; } return 0; }
2.紅與黑
有一間長方形的房子,地上鋪了紅色、黑色兩種顏色的正方形瓷磚。
你站在其中一塊黑色的瓷磚上,只能向相鄰(上下左右四個方向)的黑色瓷磚移動。
請寫一個程式,計算你總共能夠到達多少塊黑色的瓷磚。
輸入格式
輸入包括多個數據集合。
每個資料集合的第一行是兩個整數 W 和 H,分別表示 x 方向和 y 方向瓷磚的數量。
在接下來的 H 行中,每行包括 W 個字元。每個字元表示一塊瓷磚的顏色,規則如下
1)‘.’:黑色的瓷磚;
2)‘#’:白色的瓷磚;
3)‘@’:黑色的瓷磚,並且你站在這塊瓷磚上。該字元在每個資料集合中唯一出現一次。
當在一行中讀入的是兩個零時,表示輸入結束。
輸出格式
對每個資料集合,分別輸出一行,顯示你從初始位置出發能到達的瓷磚數(記數時包括初始位置的瓷磚)。
資料範圍
1≤W,H≤20
輸入樣例:
6 9
....#.
.....#
......
......
......
......
......
#@...#
.#..#.
0 0
輸出樣例:
45
解題思路:一道bfs題,由起點開始向四周擴散,加上約束標準,和訪問陣列vis,統計總共的個數,需要注意H是行,W是列,此外起點也算一個
因此一開始ans=1;
程式碼:
#include<iostream> #include<queue> #include<cstring> #include<cstdio> #define x first #define y second using namespace std; int x[]={1,-1,0,0}; int y[]={0,0,1,-1}; const int N=30; int w,h; int vis[N][N]; char map[N][N]; typedef pair<int,int> PII; bool check(int X,int Y) { if(X<0||X>=h||Y<0||Y>=w) return false; if(map[X][Y]=='#') return false; if(vis[X][Y]!=0) return false; return true; } int bfs(PII start) { int ans=1; queue<PII> q; memset(vis,0,sizeof(vis)); vis[start.x][start.y]=1; q.push(start); while(!q.empty()) { PII beg; beg=q.front(); q.pop(); for(int i=0;i<4;i++) { int X=beg.x+x[i]; int Y=beg.y+y[i]; if(check(X,Y)==false) continue; ans++; PII tem; tem.x=X,tem.y=Y; vis[X][Y]=1; q.push(tem); } } return ans; } int main() { int i,j; PII start; while(1) { scanf("%d%d",&w,&h); if(w==0&&h==0) break; for(i=0;i<h;i++) { for(j=0;j<w;j++) { cin>>map[i][j]; if(map[i][j]=='@') { start.x=i,start.y=j; } } } cout<<bfs(start)<<endl; } return 0; }
(三)圖論
1.交換瓶子
有 N 個瓶子,編號 1∼N,放在架子上。
比如有 5 個瓶子:
2 1 3 5 4
要求每次拿起 2個瓶子,交換它們的位置。
經過若干次後,使得瓶子的序號為:
1 2 3 4 5
對於這麼簡單的情況,顯然,至少需要交換 2 次就可以復位。
如果瓶子更多呢?你可以通過程式設計來解決。
輸入格式
第一行包含一個整數 N,表示瓶子數量。
第二行包含 N 個整數,表示瓶子目前的排列狀況。
輸出格式
輸出一個正整數,表示至少交換多少次,才能完成排序。
資料範圍
1≤N≤10000,
輸入樣例1:
5
3 1 2 5 4
輸出樣例1:
3
輸入樣例2:
5
5 4 3 2 1
輸出樣例2:
2
解題思路:我們的目的是讓交換的次數最少,看下圖
根據數字和對應的下標,將該數字與應該在的下標的數字相連,構成一個環,我們的目的是讓每個數字自成一個環。
當我們進行同環內的兩個數交換時:裂成兩個環
當進行不是同環內的交換時:會將兩個環合併成一個環
假設初始的環個數為k,我們要將他變成n個自環,我們需要至少n-k步操作
因此,對於本題的解法就是找出初始的環數
程式碼:
#include<iostream> using namespace std; const int N=10010; int a[N]; bool tr[N]; int main() { int i,j,n; int ans=0; cin>>n; for(i=1;i<=n;i++) cin>>a[i]; for(i=1;i<=n;i++) { if(!tr[i]) { ans++; for(j=i;!tr[j];j=a[j]) tr[j]=true; } } cout<<n-ans<<endl; return 0; }