Mô tả:
usingname space std; dequeq; int m,n,a[100001],i[100001],j[100001] int main() { scanf(“%d%d\n”,&n,&m); for (int u=1; u<=n; u++) scanf(“%d”,&a[u]); for (int k=1; k<=m; k++) scanf(“%d%d\n”,&i[k],&j[k]); for (int k=1; k<=m; k++) { for (int u=j[k-1]; u<=j[k]; j++) { while (!q.empty() && q.back()>a[u]) q.pop_back(); q.push_back(a[u]); } for (int u=i[k-1]; u #include #define maxn 100001 using namespace std; deque q; int c[maxn],m,k,i[maxn],j[maxn]; long long f[maxn]; 4 void doc() { scanf("%d%d\n",&m,&k); for (int u=1; u<=m; u++) scanf("%d",&c[u]); } void xuli() { memset(f,sizeof(f),0); // để lưu số tiền nhỏ nhất cần phải trả 2 chiếc bánh mì trong mỗi ngày for (int u=1; u<=m; u++) { i[u]=max(1,u-k+1); j[u]=u; } for (int u=1; u<=m; u++) { for (int t=j[u-1]+1; t<=j[u]; t++) { while (!q.empty() && q.back()>c[t]) q.pop_back(); q.push_back(c[t]); } for (int t=i[u-1]; t 6 #define maxn 1000001 using namespace std; deque q; int n,a[maxn],x,b[maxn],c[maxn],u[maxn],i[maxn],j[maxn],v[maxn]; void doc() { scanf("%d%d",&n,&x); for (int k=1; k<=n; k++) scanf("%d",&a[k]); } void xuli() { for (int k=1; k<=n; k++) { i[k]=k; j[k]=k+x-1; } for (int k=1; k<=n-x+1; k++) { for (int u=j[k-1]+1; u<=j[k]; u++) { while (!q.empty() && q.back()>a[u]) q.pop_back(); q.push_back(a[u]); } for (int u=i[k-1]; um) for (int j=i-m; j #define maxn 1000001 using namespace std; deque q; int n,m,c[maxn]; long long f[maxn],g[maxn]; void doc() { scanf("%d%d",&n,&m); for (int k=1; k<=n; k++) scanf("%d",&c[k]); } void xuli() { f[0]=0; g[1]=c[1]; f[1]=c[1]; g[2]=f[1]+c[2]; q.push_back(g[1]); for (int i=2; i<=n; i++) { 9 while (!q.empty() && q.back()>g[i]) q.pop_back(); q.push_back(g[i]); if (i>m) if (q.front()==g[i-m]) q.pop_front(); f[i]=q.front(); g[i+1]=f[i]+c[i+1]; } printf("%I64d",f[n]); } int main() { freopen("nasa.inp","r",stdin); freopen("nasa.out","w",stdout); doc(); xuli(); return 0; } Bài 5 Dãy dài bậc k Cho dãy số nguyên a1, a2, ..,an. Một dãy con của dãy đã cho là dãy các phần tử liên tiếp nhau. Dãy con ai, ai+1, …,aj được gọi là dãy con đều bậc k nếu như chênh lệch giữa hai số bất kỳ trong dãy này không vượt quá k. Hãy tìm dãy con đều bậc k có độ dài lớn nhất Input: sequarek.inp Dòng đầu tiên ghi hai số nguyên dương n, k (1≤n≤106,1≤k≤109) Dòng thứ hai ghi n số nguyên a1, a2, …,an (│ai│≤ 109) Output: một số nguyên duy nhất là độ dài của dãy con tìm được Example: Sequarek.inp Sequarek.out 53 4 21345 Phân tích bài toán: Để tìm được dãy con đều bậc dài nhất ta tìm đoạn [u,v] mà (v-u+1) max thỏa mãn amax-amin≤k . Ta dễ dàng có được kết quả cần tìm thông qua đoạn chương trình : res=0; for( int v=1; v<=n; v++) { fmax=-oo; fmin=oo; for (int u=v; u>=1; u--) { fmax=max(fmax,a[u]); fmin=min(fmin,a[u]); if (fmax-fmin>k ) break; } res=max(res,v-u+1); } 10 Do 1≤n≤106 nên để cải tiến chương trình ta sử dụng hàng đợi sẽ giúp chương trình chạy nhanh với n lớn. Để tiết kiếm chi phí thời gian ta dùng hai hàng đợi q và p trong đó hàng đợi q có đặc điểm phần tử đứng trước có giá trị lớn hơn hoặc bằng giá trị của phần tử đứng sau. Còn hàng đợi p có đặc điểm phần tử đứng trước có giá trị nhỏ hơn hoặc bằng giá trị của phần tử đứng sau. Để kiểm tra xem a[v]-a[u]>k ta sử dụng q.front-p.front>k . Chương trình : #include #include #define maxn 1000001 #define oo 2000000009 using namespace std; deque q,p; int a[maxn],n,k,res; void doc() { scanf("%d%d",&n,&k); for (int i=1; i<=n; i++) scanf("%d",&a[i]); } void xuli() { int u=1; for (int v=1; v<=n;v++) { while (!q.empty() && q.back()a[v]) p.pop_back(); p.push_back(a[v]); while (q.front()-p.front()>k) { if (q.front()==a[u]) q.pop_front(); if (p.front()==a[u]) p.pop_front(); u++; } res=max(res,v-u+1); } printf("%d",res); } int main() { freopen("seqarek.inp","r",stdin); freopen("seqarek.out","w",stdout); 11 doc(); xuli(); return 0; } Bài tập 6. Ếch săn mồi Có m bậc thang đánh số từ 1 đến m từ trên xuống dưới. Mỗi bậc thang được chia đều thành n ô. Ô thứ j của bậc thang i được gọi là ô (i,j) và trên đó có lượng thức ăn aij. Một con ếch muốn đi săn mồi trên những bậc thang. Ếch được xuất phát từ một ô tùy ý trên bậc thang 1 và nhảy dần xuống bậc thang m. Khi nhảy tới ô nào thì ếch sẽ ăn hết thức ăn trong ô đó. Tuy nhiên có một hạn chế là từ ô (x,y) chú ếch chỉ được phép nhảy sang ô (x’,y’) nếu: Yêu cầu : Tìm một cách đi kiếm ăn cho chú ếch sao cho tổng lượng thức ăn kiếm được là lớn nhất. Input frog.inp gồm có: - Dòng 1 chứa ba số nguyên dương m, n, k ≤1000. - M dòng tiếp theo, dòng thứ i chứa n số nguyên dương , số thứ j là aij≤109. Output frog.out gồm có: - Dòng 1 ghi tổng lượng thức ăn kiếm được. - M dòng tiếp theo, dòng thứ i ghi một số nguyên là số hiệu ô đi qua trên bậc thang i. Example Frog.inp Frog.out 352 18 43211 3 43549 5 12375 4 Phân tích bài toán:Gọi f[i][j] là số lượng thức ăn lớn nhất mà ếch kiếm được khi di chuyển đến ô (i,j) ta có công thức quy hoạch động f[i][j]= a[i][j]+max{f[i-1][j+t],t=-k..k} Đáp số cần tìm chính là max{f[m][j], j=1..n}. Để tính f[i][j] ta phải sử dụng ba vòng lặp for dẫn đến chương trình chạy chậm tức là không hiệu quả. Để giảm chi phí thời gian ta sử dụng một hàng đợi hai đầu để tính f[i][j]. Hoạt động của hàng đợi q luôn đưa vào cuối hàng đợi phần tử có giá trị lớn hơn phần tử trước đó. Phần tử đầu hàng đợi được lấy để tính mảng f khi thỏa mãn không quá k. Chương trình : #include #define maxn 1001 using namespace std; deque q; int n, m,k,a[maxn][maxn],tr[maxn][maxn],x[maxn]; long long f[maxn][maxn],res; 12 void doc() { scanf("%d%d%d",&m,&n,&k); for (int i=1; i<=m; i++) for (int j=1; j<=n; j++) scanf("%d",&a[i][j]); } void xuli() { for (int i=1; i<=n; i++) { f[1][i]=a[1][i]; tr[1][i]=0; } for (int i=2; i<=m; i++) { int u=0; int v=0; while (!q.empty()) q.pop_back(); for (int j=1; j<=n; j++) { while (v 0) { x[++slx]=id; id=tr[u][id]; u--; } printf("%I64d\n",res); for (int i=m; i>=1; i--) printf("%d\n",x[i]); } int main() { freopen("frog.inp","r",stdin); freopen("frog.out","w",stdout); doc(); xuli(); } Bài tập 7: THỜI TRANG CỦA BÒ Tại hội diễn thời trang mới nhất. Mẫu thời trang đang thịnh hành của các con bò là có hai vệt trắng trên da của nó. Nông dân John đã mua một con bò như vậy. Thật không may, thời trang luôn thay đổi nhanh chóng và bây giờ mẫu thời trang thịnh hành lại là chỉ có một vệt trắng trên da(‼!). Nông dân John muốn tạo lại mẫu thời trang mới cho con bò của mình bằng cách trộn hai vệt trắng thành một. Da của con bò có thể mô tả như là một lưới n hàng, m cột (1≤n,m≤50) gồm các kí tự dạng: ……………. ..xxxx….xxx… …xxxx….xx… .xxxx……xxx.. ……..xxxxx… ………xxx…. 14 Ở đây, kí tự ‘X’ xác định một điểm trắng. hai điểm trắng ‘X’ cùng một vệt trắng nếu nó được nối với nhau bởi một dãy các điểm chung cạnh cùng màu trắng. Có đúng hai vệt trắng như vậy. Nông dân John dùng một cái bút vẽ nhỏ để thực hiện việc trộn hai vệt trắng thành một. Cụ thể anh ta sẽ tô một số ô thành màu trắng để sau khi tô trên tấm da chỉ còn một vệt. Trong ví dụ trên anh ta cần tô 3 ô thành màu trắng (được đánh dấu bởi ‘*’ trong hình dưới đây): ……………. ..xxxx….xxx… …xxxx*...xx… .xxxx..**..xxx.. ……..xxxxx… ………xxx…. Hãy giúp nông dân John xác định số lượng ô ít nhất cần tô thêm để có được mẫu thời trang mới. Input pageant.inp gồm có: Dòng đầu tiên ghi hai số nguyên dương n, m . Dòng 2 đến n+1 : Mỗi dòng chứa một xâu kí tự độ dài m chỉ gồm kí tự ‘X’ hoặc ‘.’ Mô tả hình dạng trên da con bò. Output : pageant.out: Một số nguyên duy nhất là số lượng ô ít nhất cần phải tô thành màu trắng. Example Pageant.inp Pageant.out 6 16 3 ................ ..xxxx....xxx... ...xxxx....xx... .xxxx......xxx.. ........xxxxx... .........xxx.... Hướng dẫn: Để giải Bài toán trên ta phải tiến hành qua hai bước: B1 : Tìm số thành phần liên thông B2: Tìm đường đi ngắn nhất giữa hai thành phần liên thông. xp kt Chương trình : #include using namespace std; typedef pair II; int m,n,kq,slt,color[55][55],cl[55][55],kc[55][55]; char a[55][55]; int dh[5]={0,0,-1,0,1}; int dc[5]={0,1,0,-1,0}; deque q; 15 void doc() { scanf("%d%d",&n,&m); for (int i=1; i<=n; i++) { for (int j=1; j<=m; j++) scanf("%c",&a[i][j]); scanf("\n"); } } void bfs(int i, int j) { while (!q.empty()) q.pop_front(); q.push_back(II(i,j)); color[i][j]=slt; while (!q.empty()) { int u=q.front().first; int v=q.front().second; q.pop_front(); for(int k=1;k<=4;k++) { int u1=u+dh[k]; int v1=v+dc[k]; if(1<=u1 && u1<=n && 1<=v1 && v1<=m && a[u1][v1]=='x' && color[u1][v1]==0) { color[u1][v1]=slt; q.push_back(II(u1,v1)); } } } } void timtplt() { memset(color,0,sizeof(color)); slt=0; for (int i=1; i<=n; i++) for (int j=1; j<=m; j++) if (a[i][j]=='x' &&color[i][j]==0) { slt++; bfs(i,j); } } 16 void bfs2() { while(!q.empty()) { int u= q.front().first; int v= q.front().second; q.pop_front(); for(int k=1;k<=4;k++) { int u1=u+dh[k]; int v1=v+dc[k]; if(1<=u1 && u1<=n && 1<=v1 && v1<=m && cl[u1][v1]==0) { cl[u1][v1]=1; kc[u1][v1]=kc[u][v]+1; q.push_back(II(u1,v1)); if(color[u1][v1]==2) {kq=kc[u1][v1]-1; return;}; } } } } void timduongdi() { timtplt(); while(!q.empty()) q.pop_front(); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(color[i][j]==1) {q.push_back(II(i,j)); cl[i][j]=1;kc[i][j]=0;}; bfs2(); cout< 3. Các thao tác Hàng đợi ưu tiên (đống) là một container cho phép thực hiện các thao tác sau: + Thêm một phần tử x vào hàng đợi ưu tiên h: h.push(x). + Lấy ra khỏi hàng đợi ưu tiên phần tử có giá trị lớn nhất : h.pop(). ** Như vậy việc lấy ra và thêm vào diễn ra một đầu. + h.empty(): cho giá trị true nếu hàng đợiưu tiên không có phần tử nào, giá trị false khi hàng đợi ưu tiên không rỗng. + h.top(): cho giá trị lớn nhất của đống h. + h.size(): cho biết số lượng phần tử trong đống h. 4. Một số ví dụ về khai báo hàng đợi ưu tiên Ví dụ 1: khai báo hàng đợi ưu tiên q có các phần tử kiểu int. priority_queue q; Ví dụ 2: Khai báo hàng đợi ưu tiên q có các phần tử kiểu double; priority_ queue q; *Chú ý: Các phần tử trong hàng đợi đều phải cùng kiểu cũng có thể là một kiểu do người lập trình tự định nghĩa Ví dụ 1: Khai báo Hàng đợi ưu tiên q có các phần tử mà mỗi phần tử là một cặp dữ liệu typedef pair< int, int> II; priority_queue q; Ví dụ 2: Hàng đợi ưu tiên q có các phần tử mà mỗi phần tử là một cặp ba số nguyên: typedef pair > III; priority_queue q; 5. Bài tập áp dụng Để hiểu và vận dụng khai thác được hàng đợi ưu tiên vào giải các bài toán tôi thường cho học sinh làm một số bài tập từ đơn giản nhất đến khó dần để các em học sinh dễ tiếp thu. Bài tập 1. Hàng đợi ưu tiên Max Cho trước một danh sách rỗng. Người ta xét hai thao tác trên danh sách đo: 18 - Thao tác “+V” (ở đây V là một số tự nhiên≤109): Nếu danh sách đang có ít hơn 15000 phần tử thì thao tác này bổ sung thêm phần tử V vào danh sách , nếu không thao tác này không có hiệu lực. - Thao tác “- ”: Nếu danh sách đang không rỗng thì thao tác này loại bỏ tất cả các phần tử lớn nhất của danh sách . Nếu không thao tác này không có hiệu lực. Ví du: Với danh sách ban đầu rỗng: - Nếu ta thực hiện liên tiếp các thao tác : +1, +3, +2, +3 ta sẽ được danh sách (1,3,2,3) - Thực hiện thao tác -, ta sẽ được danh sách (1,2) - Thực hiện hai thao tác +4 ta sẽ được danh sách (1,2 ,4, 4) - Thực hiện thao tác – ta sẽ được danh sách (1,2) - Tiếp tục với các thao tác +2, +9, +7, +8 ta sẽ được danh sách (1, 2, 2, 9, 7, 8) - Cuối cùng ta thực hiện thao tác -, ta còn lại danh sách (1, 2, 2, 7, 8). Vấn đề đặt ra là cho trước một dãy không quá 100000 thao tác.Hãy xác định những giá trị số nào còn lại trong danh sách , mỗi giá trị chỉ được liệt kê một lần. Input io.inp gồm nhiều dòng, mỗi dòng ghi một thao tác . Thứ tự các thao tác trên các dòng liệt kê theo đúng thứ tự sẽ thực hiện. Output io.out gồm có 2 dòng: Dòng 1: Ghi số lượng những giá trị còn lại trong danh sách . Dòng 2: Liệt kê những giá trị đó theo thứ tự giảm dần. Example Io.inp Io.out +1 4 +3 8721 +2 +3 +4 +4 +2 +9 +7 +8 Hướng dẫn: Ta nhận thấy sau mỗi thao tác thêm vào thì danh sách mới luôn được sắp xếp theo thứ tự giảm dần, còn khi loại bỏ phần tử trong danh sách thì phần tử bị loại bỏ luôn có giá trị lớn nhất. Với bài toán trên theo cách làm thông thường thì sau khi thêm một phần tử vào danh sách ta lại phải sắp xếp lại danh sách đó theo thứ tự giảm dần. Việc đó mất nhiều thời gian vì ta phải duyệt lại cả danh sách. Ta nhận thấy dựa vào tính chất của hàng đợi ưu tiên việc đưa ra dãy số còn lại sau 100000 thao tác trở lên đơn giản nhiều. Đây chính là lợi thế của hàng đợi ưu tiên. Chương trình 19 #include #include using namespace std; int n, x[15001]; priority_queue h; char loai; void them() { scanf("%d",&n); if (h.size()<15000) h.push(n); } void bot() { scanf("\n"); if (!h.empty()) { int t=h.top(); while (t==h.top() && !h.empty()) h.pop(); } } void inkq() { int slx=0; while (!h.empty()) { x[++slx]=h.top(); while (!h.empty() && h.top()==x[slx]) h.pop(); } printf("%d\n",slx); for (int i=1; i<=slx; i++) printf("%d ",x[i]); } int main() { freopen("io.inp","r",stdin); freopen("io.out","w",stdout); while (scanf("%c",&loai)==1) { switch(loai){ case '+': them(); break; case '-':bot(); break; } } 20