You are on page 1of 46

Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.

Bài thực hành 3: Đệ quy và khử đệ quy để giải quyết một số


bài toán
Phần 1. Thực hành về đệ quy
1.1 Đệ quy - quay lui

Bài tập 3.1. Dãy Lucas được định nghĩa bởi Ln = Ln-1+ Ln-2 với L0 = 2, L1 = 1.
Hãy viết hàm tính số Lucas thứ n.
Source code:
/*
Bài tập 3.1. Dãy Lucas được định nghĩa bởi Ln = Ln-1+ Ln-2 với L0 = 2, L1 = 1.
Hãy viết hàm tính số Lucas thứ n.
*/
int lucas(int n) {
if(n<=1) return 2-n; //Tinh L0 va L1
return lucas(n-1) + lucas(n-2); //De quy de tinh Ln voi n>=2
}

Bài tập 3.2: Trên bàn cờ vua kích thước n*n có một quân mã đang ở ô (1, 1).
Hãy đưa ra một dãy các di chuyển của mã sao cho mỗi ô trên bàn cờ đều được đi
qua đúng 1 lần (ô (1, 1) được xem là đã đi qua).

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

Source code:
/*
Bài tập 3.2: Trên bàn cờ vua kích thước n*n có một quân mã đang ở ô (1, 1).
Hãy đưa ra một dãy các di chuyển của mã sao cho mỗi ô trên bàn cờ đều được đi
qua đúng 1 lần (ô (1, 1) được xem là đã đi qua).
*/
#include <iostream>
using namespace std;
int n;
int X[100], Y[100]; //# Lưu tọa độ các bước di chuyển của quân mã
int mark[100][100]; //# Đánh dấu vị trí các ô mà quân mã đã di chuyển qua

//# Mảng hx, hy mô tả 8 vị trí quân mã có thể di chuyển kể từ vị trí hiện tại
const int hx[] = {1, 1, 2, 2, -1, -1, -2, -2};
const int hy[] = {2, -2, 1, -1, 2, -2, 1, -1};

//# In ra dãy các di chuyển tìm được


void print_sol(){
for (int j = 1; j <= n * n; ++j)
printf("(%d %d)\n", X[j], Y[j]);
exit(0);
}

//# Thuật toán quay lui


void TRY(int k){
for(int i = 0; i < 8; i++){
int xx = X[k-1] + hx[i];
int yy = Y[k-1] + hy[i];
if((!mark[xx][yy]) && (1<=xx) && (xx<=n) && (1<=yy) && (yy<=n)){
X[k]=xx;//Lưu toạ độ của quân mã
Y[k]=yy;//Lưu toạ độ của quân mã
mark[xx][yy] = 1; //Đánh dấu vị trí này là ô mã đã đi qua
if(k==n*n) print_sol();//nếu đã đi qua hết đủ n*n ô thì in ra sol
else TRY(k+1); //nếu chưa đi qua hết thì thử tiếp với k+1
mark[xx][yy] = 0; //đánh dấu vị trí này là ô mã chưa đi qua
}
}
}

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

int main(){
cin >> n;
mark[1][1] = 1;
X[1] = Y[1] = 1;
TRY(2);
return 0;
}

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

1.2 Kỹ thuật nhánh cận

Bài tập 3.3. Một người xuất phát tại thành phố 1, muốn đi thăm tất cả các thành
phố khác, mỗi thành phố đúng 1 lần và quay về 1. Chi phí để đi từ thành phố i sang
thành phố j là cij. Hãy tìm tổng chi phí nhỏ nhất có thể.
Source code:
/*

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

Bài tập 3.3. Một người xuất phát tại thành phố 1, muốn đi thăm tất cả các thành
phố khác, mỗi thành phố đúng 1 lần và quay về 1. Chi phí để đi từ thành phố i sang
thành phố j là cij. Hãy tìm tổng chi phí nhỏ nhất có thể.
*/
#include <bits/stdc++.h>
using namespace std;
#define MAX 100

int n, c[MAX][MAX]; //# số thành phố và ma trận chi phí


int cmin = INT_MAX; //# chi phí đi lại nhỏ nhất giữa hai thành phố khác nhau
int best = INT_MAX; //# tổng chi phí nhỏ nhất cần tìm, ban đầu đặt bằng giá trị vô
cùng lớn INT_MAX = 2^31-1
int curr; //# tổng chi phí tới thời điểm hiện tại
int mark[MAX]; //# đánh dấu những thành phố đã đi
int x[MAX]; //# lưu giữ các thành phố đã đi

//# Đọc dữ liệu vào


void input(){
cin >> n;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j){
cin >> c[i][j];
if (c[i][j] > 0) cmin = min(cmin, c[i][j]);
}
}

//# Thuật toán quay lui


void TRY(int k){
for(int i = 2; i <= n; i++){
if(!mark[i]){
x[k] = i; //lưu giữ thành phố i
mark[i] = 1;//đánh dấu là đã đi qua thành phố i
curr += c[x[k-1]][i];//cộng vào chi phí sau khi đi qua thành phố i
if(k==n) best = min(best, curr+c[i][1]);
//nếu đã đi qua hết các thành phố, tính best theo min của hai chi phí
else if (curr + cmin * (n-k+1) < best) TRY(k+1);
//nếu chưa đúng thì thử với k+1
mark[i] = 0;//đánh dấu là chưa đi qua thành phố i
curr -= c[x[k-1]][i];//trừ chi phí để tính lại

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

}
}
}

int main() {
input();
x[1] = 1;
TRY(2);
cout << best;
return 0;
}

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

1.3 Đệ quy có nhớ

Bài tập 3.4. Cho dãy a có n phần tử. Một dãy con của a là dãy thu được bằng cách
xóa đi một số phần tử của a và giữ nguyên thứ tự các phần tử còn lại (có thể không
xóa phần tử nào). Hãy tìm dãy con tăng dài nhất của a.
Source code:
/*
Bài tập 3.4. Cho dãy a có n phần tử. Một dãy con của a là dãy thu được bằng cách
xóa đi một số phần tử của a và giữ nguyên thứ tự các phần tử còn lại (có thể không
xóa phần tử nào). Hãy tìm dãy con tăng dài nhất của a.
*/
#include <bits/stdc++.h>
using namespace std;
int a[1000], n;
int mem[1000]; //# mảng ghi nhớ lời giải các bài toán con đã được giải

void init(){
memset(mem, -1, sizeof(mem));
}

//# Quy hoạch động,


//# Hàm lis(i) trả về độ dài dãy con tăng dài nhất kết thúc bởi a[i]
int lis(int i) {

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

mem[0] = 1;
for(int i=1; i<n; i++){
mem[i] = 1;
for(int j=0; j<i; j++){
if(a[j] < a[i]){ //nếu a[j] < a[i]
if(mem[i] < mem[j] + 1){ //nếu mem[i] < mem[j] + 1
mem[i] = mem[j] + 1; //thì ta sẽ cộng 1 vào giá trị mem[j]
}
}
}
}
return mem[i]; //trả về mem[i]
}

//# Truy vet loi giai


void trace(int i){
for(int j = 0; j < i; j++){
if (a[j] < a[i] && mem[i] == 1 + mem[j]){
trace(j);
break;
}
}
cout << a[i] << " ";
}

int main(){
init();
cin >> n;
for(int i = 0; i < n; i++) cin >> a[i];
int res = 1, pos = 0;
for(int i = 1; i < n; i++){
if (res < lis(i)){
res = lis(i);
pos = i;
} //hàm for để xác định dãy tăng, đặt lại vị trí để đếm số lượng phần tử
}
cout << res << endl;
trace(pos);
return 0;

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

Phần 2. Khử đệ quy

Bài tập 3.5. Tính hệ số tổ hợp C(n, k)


Source code:
/*
Bài tập 3.5. Tính hệ số tổ hợp C(n, k)
*/
#include <iostream>
using namespace std;

int binom(int n, int k) {


if (k > n) return 0;
if (k == 0) return 1;
return binom(n-1, k) + binom(n-1, k-1);
}

int binom2(int n, int k){


//# Khử đệ quy
long result = 1; //(nC0 = 1) //gán giá trị nC0 = 1
for ( int i = 1; i <= k ; i++)
{
result = result * n / i; //tính lại hệ số tổ hợp

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

n--; //giảm giá trị của n để tính các hệ số tiếp theo


}
return result;
}

int main() {
int m;
cin >> m;
for (int n = 1; n <= m; ++n){
for (int k = 0; k <= n; ++k)
printf("%d ", binom(n, k)); // in ra n dòng hệ số tổ hợp
printf("\n");
}
for (int n = 1; n <= m; ++n){
for (int k = 0; k <= n; ++k)
printf("%d ", binom2(n, k));// in ra n dòng hệ số tổ hợp bằng cách 2
printf("\n");
}
return 0;
}

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

Bài tập 3.6. Tìm ước chung lớn nhất của hai số nguyên a, b cho trước.
Source code:
/*
Bài tập 3.6. Tìm ước chung lớn nhất của hai số nguyên a, b cho trước.
*/
#include <iostream>

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

using namespace std;

int gcd(int a, int b){


if (b == 0) return a;
return gcd(b, a % b); //tính gcd theo giải thuật Euclid
}

int gcd2(int a, int b){

//# Khử đệ quy


while(b != 0){ //nếu b khác 0
int tmp = a % b; //tìm số dư của a khi chia cho b
a = b; //gán b vào a
b = tmp;//gán tmp vào b
}
return a; //trả về a sau khi thực hiện giải thuật
}

int main() {
int a, b;
cin >> a >> b;
cout << gcd(a, b) << endl << gcd2(a, b); //in ra giá trị gcd
return 0;
}

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

Bài tập 3.7. Sử dụng phương pháp khử đệ quy bằng stack, hãy liệt kê các xâu nhị
phân độ dài n không có k bit 1 nào liên tiếp.
Source code:
/*
Bài tập 3.7. Sử dụng phương pháp khử đệ quy bằng stack, hãy liệt kê các xâu nhị
phân độ dài n không có k bit 1 nào liên tiếp.
*/
#include <bits/stdc++.h>
using namespace std;

struct state{
int i, j, old_L;
//# constructor
state(int _i = 0, int _j = 0, int _L = 0):

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

i(_i), j(_j), old_L(_L){}


};//trường state

int main() {
int n, k;
cin >> n >> k;
int x[n+1];
stack<state> s;
//# number of consecutive suffix 1
int L = 0;
s.push(state(1, 0));
while (!s.empty()){
state &top = s.top();
//# if a new binary sequence is found
if (top.i > n){
for (int i = 1; i <= n; ++i)
cout << x[i] << " \n"[i == n];
s.pop();
continue;
}

//# Khử đệ quy


if(top.j>0) L = top.old_L;
//nếu giá trị của top.j lớn hơn 0, thì L sẽ được gán giá trị trên

if(top.j>1){ //nếu top.j > 1


s.pop();//loại bỏ một phần tử khỏi đỉnh ngăn xếp
continue;//vòng lặp tiếp tục
}

if(L+1<k || top.j==0){
x[top.i] = top.j;//gán giá trị của top.i cho phần tử top.j
top.old_L = L;//lưu giá trị
if(top.j){ //nếu top.j khác 0
L = L + 1;//tăng giá trị của 0
} else {L = 0;}//nếu không, L = 0
s.push(state(top.i+1,0));
//thêm một trạng thái mới vào ngăn xếp, top.i tăng 1, top.j về 0
}

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

top.j++; //tăng giá trị top.j lên 1


}
return 0;
}

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

Bài tập 3. 8. Cân đĩa


Bạn đang muốn kiểm tra xem một vật cho trước có đúng nặng M như người ta nói
hay không. Có một cân thăng bằng và n quả cân. Quả thứ i nặng mi. Hãy chỉ ra
một cách cân thỏa mãn. Quy cách in ra đã được tích hợp trong mã nguồn dưới.
Source code:
/*
Bài tập 3.8. Cân đĩa
Bạn đang muốn kiểm tra xem một vật cho trước có đúng nặng M như người ta nói
hay không. Có một cân thăng bằng và n quả cân. Quả thứ i nặng mi. Hãy chỉ ra
một cách cân thỏa mãn. Quy cách in ra đã được tích hợp trong mã nguồn dưới.
*/
#include <bits/stdc++.h>
using namespace std;

struct state{
int i, j, s;
state(int _i = 0, int _j = 0, int _s=0): i(_i), j(_j), s(_s){}
};//khai báo cấu trúc state để xử lý các đối tượng

int main() {
int n, M;
cin >> n >> M; //nhập các giá trị đầu vào của các cân
int m[n+1];//khai báo mảng m có n + 1 giá trị
for (int i = 1; i <= n; ++i) cin >> m[i];
int x[n+1];
stack<state> s;
//# sum of selected weights
int sum = 0;
s.push(state(1, -1, -m[1])); //đưa vào ngăn xếp state để bắt đầu duyệt
s.push(state(1, 0, 0)); //đưa vào ngăn xếp một state khác để tiếp tục duyệt
s.push(state(1, 1, m[1]));//đưa vào ngăn xếp một state khác để tiếp tục duyệt
while (!s.empty()){ //lặp đến khi ngăn xếp trống
state top = s.top();//lấy phần tử đầu tiên của ngăn xếp gắn cho top
if (top.i >= n){ //kiểm tra độ lớn của top.i
if (top.s == M){
for (int i = 1; i <= n; ++i){
if (x[i] == -1) cout << '-' << m[i];//in ra kết quả của bài toán
if (x[i] == 1) cout << '+' << m[i];//in ra kết quả của bài toán

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

}
cout << "=" << M;
exit(0);
}
s.pop();
continue; //nếu vòng lặp đúng, loại bỏ phần tử ở trên đỉnh, và tiếp tục
}

//# Khử đệ quy


s.pop();//loại bỏ phần tử đỉnh
x[top.i]=top.j;//gắn giá trị top.j cho x[top.i]
s.push(state(top.i+1, -1, top.s-m[top.i+1]));
s.push(state(top.i+1, 0,top.s));
s.push(state(top.i+1, 1,top.s+m[top.i+1])); //thực hiện quá trình thêm phần tử
vào đỉnh ngăn xếp
}
cout << -1; //in ra -1 nếu không có kết quả đúng

return 0;
}

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

Phần 3. Bài tập về nhà

Bài tập 3.9. Lập lịch cho y tá


Một y tá cần lập lịch làm việc trong N ngày, mỗi ngày chỉ có thể là làm việc hay
nghỉ ngơi. Một lịch làm việc là tốt nếu không có hai ngày nghỉ nào liên tiếp và mọi
chuỗi ngày tối đại làm việc liên tiếp đều có số ngày thuộc đoạn [k1, k2]. Hãy liệt
kê tất cả các cách lập lịch tốt, với mỗi lịch in ra trên một dòng một xâu nhị phân độ
dài n với bit 0/1 tương ứng là nghỉ/làm việc. Các xâu phải được in ra theo thứ tự từ
điển.
Source code:
/*
Bài tập 3.9. Lập lịch cho y tá
Một y tá cần lập lịch làm việc trong N ngày, mỗi ngày chỉ có thể là làm việc hay
nghỉ ngơi. Một lịch làm việc là tốt nếu không có hai ngày nghỉ nào liên tiếp và mọi
chuỗi ngày tối đại làm việc liên tiếp đều có số ngày thuộc đoạn [k1, k2]. Hãy liệt
kê tất cả các cách lập lịch tốt, với mỗi lịch in ra trên một dòng một xâu nhị phân độ
dài n với bit 0/1 tương ứng là nghỉ/làm việc. Các xâu phải được in ra theo thứ tự từ
điển.
*/
#include<bits/stdc++.h>
using namespace std;
const int MAX = 1000;
const int du = 1000000000 + 7;
int n, k1, k2;
int x[MAX];
int cnt = 0; //biến cnt để đếm số lượng kết quả
int so0 = 0, so1 = 0; //khai báo các biến cần thiết

void inputData(){
cin >> n >> k1 >> k2;//nhập dữ liệu k1, k2 là số ngày nghỉ cho phép
}

bool check(int a, int i){


if(a==1) return true; //Kiểm tra xem a có phải ngày làm việc không
else {
//Nếu a không phải ngày làm việc, tức a là ngày nghỉ, ngày kế tiếp và trước đó
không thể là ngày nghỉ
if(i==0){
if(x[a-1] == 0) return false;

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

else { //ngày trước đó không là ngày nghỉ


if(so1<k1) return false;
}
} else { // i==1
if(x[a-1] == 0){
if(n-a+1 < k1) return false;
} else {
if(so1>=k2) return false;
}
}

return true;
}
}

void solution(){
/*
khai báo int so1 = 0;
chạy for cho đến khi so1 khác 0 và nhỏ hơn k1
*/

for(int i=1; i<=n; i++)


cout << x[i];
cout << endl;
cnt++;
//nếu (cnt == du) cnt = 0;
}

void TRY(int a){


for(int i=0; i<=1; i++){
if(check(a,i)){
x[a] = i; // ngày thứ a có thể là làm việc hoặc không làm việc
int pre = so1;//gán biến
if(i == 1){
if(x[a-1] == 1) so1++;
else so1 = 1;
} else {
so1 = 0;
}

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

if(a==n) solution();
else TRY(a+1);
so1 = pre;
}
}
}

int main(){
inputData();
TRY(1);
//cout << cnt % du;
return 0;
}

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

Bài tập 3.10. Khoảng cách Hamming


Khoảng cách Hamming giữa hai xâu cùng độ dài là số vị trí mà ký tự tại vị trí đó là
khác nhau trên hai xâu. Cho S là xâu gồm n ký tự 0. Hãy liệt kê tất cả các xâu nhị
phân độ dài n, có khoảng cách Hamming với S bằng H. Các
xâu phải được liệt kê theo thứ tự từ điển.
Source code:
/*
Bài tập 3.10. Khoảng cách Hamming
Khoảng cách Hamming giữa hai xâu cùng độ dài là số vị trí mà ký tự tại vị trí đó là
khác nhau trên hai xâu. Cho S là xâu gồm n ký tự 0. Hãy liệt kê tất cả các xâu nhị
phân độ dài n, có khoảng cách Hamming với S bằng H. Các
xâu phải được liệt kê theo thứ tự từ điển.
*/
#include<bits/stdc++.h>
using namespace std;
const int MAX = 20;
int N, H;
int x[MAX];
int S[MAX]; //Xâu mẫu để so sánh
int cnt = 0;

void input(){

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

cin >> N >> H;


for(int i=0; i<N; i++)
S[i] = 0; //Khởi tạo xâu mẫu ban đầu bằng 0
for(int i=0; i<N; i++)
x[i] = 0; //Khởi tạo x ban đầu bằng 0
}

// Kiểm tra khoảng cách Hamming giữa hai xâu


int checkHamming(int str1[], int str2[]){
int lens = N;
int cnt = 0;
for(int i=0; i<lens; i++){
if(str1[i] != str2[i]) cnt++; //nếu phần tử i khác nhau, cộng cnt thêm 1
}
return cnt;
}

bool check(int a, int i){


return true;
}

void solution(){
if(checkHamming(x,S) == H){ //nếu khoảng cách Hamming bằng H
for(int i=0; i<N; i++)
cout << x[i];//liệt kê tất cả các xâu thoả mãn, theo thứ tự từ điển
cout << endl;
}
}

void TRY(int a){


for(int i=0; i<=1; i++){
x[a] = i; //thử gán giá trị i cho phần tử x[a]
if(a == N-1) solution(); //nếu đã gán đủ hết các giá trị, in ra solution
else
TRY(a+1); //nếu chưa gán đủ, thì gán cho giá trị tiếp theo
}
}

int main(){

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

int T;
cin >> T;
while(T > 0){
input();
TRY(0);//Bắt đầu TRY phần tử đầu tiên với vòng while T>0
T--;
}
}

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

Bài tập 3.11. Lịch trình chụp ảnh


Superior là một hòn đảo tuyệt đẹp với n địa điểm chụp ảnh và các đường một chiều
nối các điểm chụp ảnh với nhau. Đoàn khách tham quan có r người với sở thích
chụp ảnh khác nhau. Theo đó, mỗi người sẽ đưa ra danh sách các địa điểm mà họ
muốn chụp. Bạn cần giúp mỗi người trong đoàn lập lịch di chuyển sao cho đi qua
các điểm họ yêu cầu đúng một lần, không đi qua điểm nào khác, bắt đầu tại điểm
đầu tiên và kết thúc tại điểm cuối cùng trong danh sáchmà họ đưa ra, và có tổng
khoảng cách đi lại là nhỏ nhất.
Source code:

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

/*
Bài tập 3.11. Lịch trình chụp ảnh
Superior là một hòn đảo tuyệt đẹp với n địa điểm chụp ảnh và các đường một chiều
nối các điểm chụp ảnh với nhau. Đoàn khách tham quan có r người với sở thích
chụp ảnh khác nhau. Theo đó, mỗi người sẽ đưa ra danh sách các địa điểm mà họ
muốn chụp. Bạn cần giúp mỗi người trong đoàn lập lịch di chuyển sao cho đi qua
các điểm họ yêu cầu đúng một lần, không đi qua điểm nào khác, bắt đầu tại điểm
đầu tiên và kết thúc tại điểm cuối cùng trong danh sáchmà họ đưa ra, và có tổng
khoảng cách đi lại là nhỏ nhất.
*/
#include<bits/stdc++.h>
using namespace std;
const int MAX = 10000;
int n, r;
int price[MAX][MAX];
int x[MAX];
bool visited[MAX];
vector<int> vt;
int min_price;
int sum_price;
int start, destination, numberOfPoint;
//Khai báo các biến cần thiết để đánh dấu giá tiền, tổng tiền, các địa điểm đã thăm,
điểm bắt đầu
void input(){
cin >> n >> r; //Nhập số lượng địa điểm và số lượng người
for(int i=0; i<n; i++)
for(int j=0; j<n; j++){
cin >> price[i][j]; //nhập dữ liệu về giá giữa hai địa điểm i và j
}
}

bool check(int a, int i){


if(visited[vt[i]]) return false;
if(price[x[a-1]][vt[i]] == 0) return false; //kiểm tra xem có thể đi từ x[a-1] đến
vt[i] không
return true;
}

void solution(){

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

if(price[x[numberOfPoint-2]][destination] == 0) return;
min_price = min(min_price, sum_price + price[x[numberOfPoint-2]]
[destination]);
//kiểm tra và lưu trữ giá tiền nhỏ nhất dựa trên tổng số điểm và địa điểm
}

void TRY(int a){ //Thử nghiệm phương án tối ưu nhất


for(int i=1; i<numberOfPoint-1; i++){
if(check(a, i)){
visited[vt[i]] = true;
sum_price += price[x[a-1]][vt[i]];;//nếu đã đi qua vt[i], cộng thêm tiền đi
lại giữa 2 vị trí

x[a] = vt[i];//gán tiếp x[a] thành vt[i]


if(a == numberOfPoint-2) solution(); //nếu đã đi đủ các địa điểm, in ra chi
phí
else TRY(a+1);//nếu chưa, thử tiếp phương án kế tiếp

visited[vt[i]] = false; //gán là chưa thăm quan vt[i]


sum_price -= price[x[a-1]][vt[i]];//trừ đi chi phí thăm quan vt[i]
}
}
}

int main(){
string str;
input(); getline(cin,str);//input lịch di chuyển
while(r > 0){
min_price = INT_MAX;
sum_price = 0;
getline(cin, str);
while (!str.empty()){
stringstream convert(str.substr(0, str.find(" ")));
int tmp = 0;
convert >> tmp;
vt.push_back(tmp - 1);

if (str.find(" ") > str.size()){


break;

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

} else {
str.erase(0, str.find(" ") + 1); //Cập nhật lịch di chuyển
}
}
//Khởi tạo các input cần thiết
start = vt[0]; // điểm bắt đầu
destination = vt[vt.size()-1]; // điểm đích
numberOfPoint = vt.size(); // số điểm cần đi qua
x[0] = start; x[numberOfPoint-1] = destination;
for(int i=0; i<n; i++)
visited[i] = false; //chạy thử đi qua các vị trí

TRY(1);

//In ra kết quả


if(min_price == INT_MAX) cout << "0" << endl;
else cout << min_price << endl;
//Xoá vector khách đang thử, chạy sang khách tiếp theo
vt.erase(vt.begin(), vt.end());
r--;
}
}

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

Bài tập 3.12. Đếm đường đi


Cho đồ thị vô hướng G, hãy đếm số đường đi đi qua k cạnh và không đi qua đỉnh
nào quá một lần.
Source code:
/*
Bài tập 3.12. Đếm đường đi
Cho đồ thị vô hướng G, hãy đếm số đường đi đi qua k cạnh và không đi qua đỉnh
nào quá một lần.
*/
#include<bits/stdc++.h>
using namespace std;
const int MAX = 100;
int n, k;
int m;
vector<vector<int> > vt; // chuyển các cạnh sang lưu trữ dưới dạng danh sách kề
int x[MAX];
bool visited[MAX];
int res;

void input(){ //Nhập dữ liệu n, m, k


cin >> n >> k;
cin >> m;
vt.resize(n+1);
for(int i=0; i<m; i++){
int tmp1, tmp2;
cin >> tmp1 >> tmp2; //nhập vào hai đỉnh để tham chiếu

vt[tmp1-1].push_back(tmp2-1); // thêm phần tử mới vào ngăn xếp là tmp2, kề


với tmp1
vt[tmp2-1].push_back(tmp1-1); // thêm phần tử mới vào ngăn xếp là tmp1, kề
với tmp2
}

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

for(int i=0; i<n; i++){


visited[i] = false;
}
res = 0;
}

bool check(int a, int i){


if(a == 0) return true; //Đỉnh xuất phát, visited luôn bằng true
if(visited[i]) return false; //Nếu đỉnh đã được thăm, vi phạm bài toán, trả về false

int index = 0;
for(int j=0; j<vt[x[a-1]].size(); j++){ //kiểm tra xem có cạnh nối giữa 2 đỉnh hay
không
if(i == vt[x[a-1]][j]) index++; //nếu có, index cộng 1
}
if(index == 0) return false; //không thì trả về false

return true;
}

void solution(){
res++; //khi thoả mãn, cộng 1 vào res, in ra solution
}

void TRY(int a){ //Thử tất cả các khả năng chọn đỉnh, chọn cạnh
for(int i=0; i<n; i++){
if(check(a, i)){
visited[i] = true;//nếu đã thăm đỉnh i
x[a] = i; //gán i vào x[a]

if(a == k) solution(); //nếu đã có đủ k cạnh thì in ra solution


else TRY(a+1);//chưa đủ thì thử tiếp trường hợp tiếp theo

visited[i] = false; //trả lại về false


}
}
}

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

int main(){
input();
TRY(0); //thử bắt đầu từ đỉnh xuất phát
cout << res / 2; //in ra số lượng đường đi cần tìm
}

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

Báo cáo thực hành số 3


Phạm Đình Tú – MSSV: 20210888 – IT3040 – 732826 – 2023.1

Báo cáo thực hành số 3

You might also like