Một số ghi chú Các đoạn code mẫu trong chuyên đề được sử dụng thống nhất một số câu lệnh rút gọnđể dễ đọc hơn, vui lòng tham khảo một số khai báo dưới đây: #include #define oo 1000000007
Trang 1Chuyên đề
Bitmask và ứng dụng
Học sinh thực hiện: Nguyễn Huỳnh Yến Nhi
Lớp: 11A2 Giáo viên hướng dẫn: Nguyễn Hoàng Phú Năm học: 2021 – 2022
Cần Thơ, tháng 3 năm 2022
Trang 2MỤC LỤC
A MỞ ĐẦU 3
1 Lý do và mục đích chọn đề tài 3
2 Tóm tắt đề tài 3
3 Kết quả cần đạt 3
4 Một số ghi chú 3
B LÝ THUYẾT 3
1 Định nghĩa về Bitmask 4
2 Một số thao tác thông dụng khi sử dụng bit 4
3 Ứng dụng của Bitmask 6
C MỘT SỐ BÀI TẬP 7
1 BCSINH – SINH CÁC DÃY NHỊ PHÂN ĐỘ DÀI N 7
1.1 Đề bài 7
1.2 Ý tưởng 8
1.3 Code mẫu 8
2 BCMAGIC 9
2.1 Đề bài 9
2.2 Ý tưởng 9
2.3 Code mẫu 9
3 CHỌN Ô 11
3.1 Đề bài 11
3.2 Ý tưởng 11
3.3 Code mẫu 11
D KẾT LUẬN 13
1 Kết quả đạt được 13
2 Khó khăn - Nhược điểm 13
E TÀI LIỆU THAM KHẢO 13
Bitmask và ứng dụng Trang 2
Trang 3A MỞ ĐẦU
1 Lý do và mục đích chọn đề tài
Đối với tin học, việc tìm hiểu Bit là vô cùng cần thiết Tuy đã được tiếp cận với Bit nhưng em vẫn còn khá mơ hồ và vẫn chưa hiểu rõ về nó Vì vậy em đã chọn chuyên đề này để nhằm củng cố kiến thức để có một nền tảng vững chắc về Bit, cải thiện tư duy, cũng như là trao dồi thêm kĩ năng để giải quyết các lớp bài toán này
2 Tóm tắt đề tài
Chuyên đề “Bitmask và ứng dụng” sẽ đề cập đến các nội dung sau:
Giới thiệu, nêu định nghĩa của Bitmask
Một số thao tác thông dụng khi sử dụng Bitmask
Một số ứng dụng của Bitmask
Bài tập ứng dụng
3 Kết quả cần đạt
Hiểu được Bitmask là gì ?
Sử dụng được một số thao tác thông dụng của Bitmask
Tìm hiểu một số ứng dụng của Bitmask
Áp dụng Bitmask để giải một số bài toán cơ bản
4 Một số ghi chú
Các đoạn code mẫu trong chuyên đề được sử dụng thống nhất một số câu lệnh rút gọn
để dễ đọc hơn, vui lòng tham khảo một số khai báo dưới đây:
#include <bits/stdc++.h>
#define oo 1000000007
#define ii pair<int, int>
#define ff first
#define ss second
#define vi vector<int>
#define vii vector<ii>
#define pb push_back
#define mp make_pair
#define fto(i, a, b) for (int i = a; i <= b; ++i)
#define fdto(i, a, b) for (int i = a; i >= b; i)
Trang 4B LÝ THUYẾT
1 Định nghĩa về Bitmask
- Bitmask là một dãy bit thể hiện nhiều trạng thái của một đối tượng Chúng ta sẽ sử dụng một số nguyên để biểu diễn một tập hợp với miền 32 giá trị (hoặc 64 nếu sử dụng
số nguyên 64 bit)
- Bây giờ hãy tưởng tượng bạn muốn tạo một chương trình chứa một trạng thái, trạng thái này dựa trên nhiều giá trị là một hoặc không Người ta có thể lưu trữ các giá trị này trong các biến khác nhau, chúng có thể là boolean hoặc số nguyên Hoặc thay vào đó ,
sử dụng một biến số nguyên duy nhất và sử dụng từng bit trong số 32 bit bên trong của
nó để biểu diễn giá trị một và không khác nhau Điều này làm cho Bitmask trở thành một trong những sự lựa chọn tối ưu nhất trong lập trình thi đấu
2 Một số thao tác thông dụng khi sử dụng bit.
2.1 Phép NOT (ký hiệu ~), AND (ký hiệu &), OR (ký hiệu |), XOR (ký hiệu ^)
- Phép NOT ( ): pháp NOT thực hiện thao tác lấy phủ định từng bit, nghĩa là bit 0
sẽ trở thành 1 và ngược lại Ví dụ: 10001 01110=
- Phép AND (¿): phép AND thực hiện thao tác lấy hai dãy bit có độ dài bằng nhau
rồi so sánh từng cặp bit tương ứng với nhau Nếu cả hai bit đều là 1 thì kết quả của thao tác này sẽ là 1, tất cả trường hợp còn lại đều trả về kết quả là 0 Ví dụ:
10011 11000∧ =100 00S
- Phép OR (¿): Phép OR thực hiện thao tác lấy hai dãy bit có độ dài bằng nhau rồi
so sánh từng cặp bit tương ứng với nhau Nếu ít nhất một trong hai bit là 1 thì kết quả của thao tác này sẽ là 1, ngược lại khi cả hai bit đều là 0 thì kết quả của thao tác sẽ là 0 Ví dụ: 10010 10101∨ =10111
- Phép XOR (¿): Phép XOR thực hiện thao tác lấy hai dãy bit có độ dài bằng nhau
rồi so sánh từng cặp bit tương ứng với nhau Nếu cả hai bit là 1 hoặc cả hai bit là 0
thì kết quả sẽ là 0, ngược lại khi chỉ có một trong hai bit là 1 thì kết quả sẽ là 1 Ví dụ: 1001110101 00110
- Bảng chân trị trong Bitmask: là một bảng toán học thể hiện giá trị kết quả của
các toán tử thao tác bit bên trên,
Dưới đây là bảng chân trị:
Bitmask và ứng dụng Trang 4
Trang 50 0 1 0 0 0
Phép dịch trái n bit tương đương với phép nhân cho 2n
Ví dụ
2.3 Phép dịch phải (Ký hiệu ¿):
Phép dịch trái n bit tương đương với phép chia cho 2n
Ví dụ:
Ví dụ: 21 ¿ (1 ¿ 2)
A = 1 0 1 0 1
&
1 ¿ 2 = 0 0 1 0 0
0 0 1 0 0
2.5 Gán bit thứ i bằng 1 (Set bit): A |= (1 << i):
0
Trang 6Ví dụ: 21 | (1≪1)
A = 1 0 1 0 1
|
1 ¿ 1 = 0 0 0 1 0
1 0 1 1 1
Ví dụ: 21∧ (1≪2)
A = 1 0 1 0 1
&
~(1 << 2) = 1 1 0 1 1
1 0 0 0 1
3 Quy hoạch động Bitmask
Quy hoạch động trạng thái bằng kĩ thuật đánh dấu bit:
Kĩ thuật này sử dụng cho các bài toán có dạng trạng thái chứa các tập hợp con của phần
tử, vì vậy trạng thái có thể được lưu trữ dưới dạng số nguyên Trạng thái hiện tại sẽ cập nhật từ
dữ liệu của trạng thái trước
Xét bài toán: Sắp tới John cần chuẩn bị quà cho n người bạn và cửa hàng có n món quà (n ≤ 20) được đánh số từ 1 đến n Để tặng cho người i món quà j cần một chi phí là a [i][ j]> 0 John cần phải lựa chọn quà cho mỗi người và mỗi phần quà chỉ tặng cho một người sao cho chi phí là ít nhất
- Ta gọi f [i][ ] sttlà tổng giá trị nhỏ nhất khi John chọn quà cho những người từ 0 đến k –
1, và stt là một số nhị phân với mỗi bit món quà thứ j (Nếu bit thứ jbật thì nhiệm vụ thứ
jđược chọn, ngược lại thì nhiệm vụ thứ jđã được chọn)
Với mỗi trạng thái f [i]¿], ta sẽ cần tìm quà cho người thứ j , và đương nhiên,
ta chỉ được chọn món quà k mà chưa được chọn vì John không muốn chọn trùng quà (bit thứ k chưa bật) Nhưng ta lại nhận thấy rằng i lại đúng bằng số bit bật của stt, có nghĩa là số bit được bật trong stttượng trưng số người đã được John chọn quà, việc đó ta cũng đảm bảo việc duyệt qua tất cả các trạng thái Do đó, ta lại có công thức sau:
Bitmask và ứng dụng Trang 6
f [stt ∨(1≪ j)]=min (f [stt∨(1≪ j , f [stt a[cnt i)] ]+ ][ ]) (với cnt là số bit bật trong mask)
Trang 7int getBit(int i, int n){
return n ( >> i ) & 1
}
*
int cntBit(int stt){
int cnt = 0
fto i, , ( 0 2){
if (getBit(i, stt)) ++cnt;
}
return cnt;
}
int main(){
cin >> n;
fto i, , n( 0 - ) {
fto j, , n( 0 - ) {
}
}
*
fto(stt, 0, ( << n) 1
int cnt cntBit= (stt)
fto j, , n( 0 - ) {
f[stt|(1 << j)] min= ( [stt|(1 << j)], f[stt] a+ [cnt][j]); }
cout << f[(1 << n ) - 1 ;
}
*
*
C MỘT SỐ BÀI TẬP
1 BCSINH – SINH CÁC DÃY NHỊ PHÂN ĐỘ DÀI N
1.1 Đề bài
Sinh các dãy nhị phân có độ dài n.
Input:
- Một số nguyên n (1 ≤n≤9)
Output:
Trang 8- Mỗi dòng một dãy nhị phân Các dãy nhị phân phải được liệt kê theo thứ tự từ điển
0 1
1 0
1 1
1.2 Ý tưởng
- Ta có được 2n dãy nhị phân được liệt kê từ 0 đến 2n−1 Ta sử dụng một hàm getBit(i ,n) cho biết bit thứi của dãy thứ n có giá trị là 0 hay 1, sau đó in ra 1.3 Code mẫu
int getBit(int i, int n){
return n ( >> i ) & 1
}
int main(){
fto stt, , ( 0 ( << n) 1
fdto bit, n( - , 0) {
if (getBit(bit, stt))
cout << "1";
}
else cout << "0";
cout << \n";
}
}
return 0
}
2 CHỌN Ô
2.1 Đề bài
Cho một bảng hình chữ nhật kích thước 4×n ô vuông Các dòng được đánh số từ
1đến 4 , từ trên xuống dưới, các cột được đánh số từ 1đến n từ trái qua phải
Bitmask và ứng dụng Trang 8
Trang 9Ô nằm trên giao của dòng i và cột j được gọi là ô(i , j) Trên mỗi ô (i , j) có ghi một số nguyêna i , j , i =1,2,3,4 ; j=1,2 , … , n Một cách chọn ô là việc xác định một tập con khác rỗng S của tập tất cả các ô của bảng sao cho không có hai ô nào trong S có chung cạnh Các ô trong tậpS được gọi là ô được chọn, tổng các số trong các ô được chọn được gọi là trọng lượng của cách chọn Tìm cách chọn sao cho trọng lượng là lớn nhất
Input:
- Dòng đầu tiên chứa số nguyên dương n là số cột của bảng(1 ≤n ≤ 10000)
- Cột thứ j trong số n cột tiếp theo chứa 4 số nguyên a1 , j , a2 , j , a3, j , a4, j, hai số liên
tiếp cách nhau ít nhất một dấu cách, là 4 số trên cột j của bảng a ij ≤30000¿ Output:
- Gồm 1 dòng duy nhất là trọng lượng của cách chọn tìm được
3
-1 9 3
-4 5 -6
7 8 9
9 7 2
32
2.2 Ý tưởng
- Đối với bài toán này ta có thể sử dụng Quy hoạch động Bitmask
- Gọi f[i][j] là tổng số trọng lượng của cột thứ i trạng thái j
- Một trạng thái hợp lệ là trạng thái khi ghép với trạng thái phía trước nó thì không
có 2 bit kề nhau không đồng thời bằng 1
2.3 Code mẫu
Trang 10#define maxN 100005
using namespace std;
int n, f[maxN][16], a[ ][maxN , r, k;]
int getBit(int i, int n) {
return n >> i) & 1
}
int vaild(int n ) {
fto i, , ( 0 2
if ((getBit(i, n getBit) & ( + , n)) == ) return 0
}
return ;
}
int main() {
ios_base::sync_with_stdio(false); cin.tie(NULL)
int dem , ans1 = 0 = -oo;
fto i, , ( 0 3
fto j, , n( 1 ) {
cin >> a[ ][ ];j
if a i][j ) ++dem;
ans1 max ans1, a= ( [ ][ ]);j
}
}
if dem == *n ) {
cout << ans1;
return ;
}
fto col, , n ( 1 ) {
fto stt, , ( 0 15) {
if vaild stt( )) {
f[col][stt] = 0
int val = 0
fto i, , ( 0 3 {
if getBit(i, stt)) val + a[ ][col]
}
fto prv_stt, , ( 0 15) {
if ((stt prv_stt& ) == ) {
f[col][stt] max= ( [col][stt], val f+
[col-1][prv_stt]);
}
}
}
}
}
int ans = 0
*
fto stt, , ( 0 15) ans max ans, f= ( [ ][stt]);
*
return ;
}
Bitmask và ứng dụng Trang 10
Trang 113 BCMAGIC
3.1 Đề bài
Cô công chúa đã đi đến Celestia và Lâu đài của Luna để tìm kiếm chiếc rương kho báu của chú kì lân Dãy số nguyên dương b i là hài hòa khi và chỉ khi với mỗi hai phần tử của dãy
có ước chung lớn nhất của chúng bằng 1 Theo câu truyện thần thoại có thật ngày xưa, mật khẩu của chiếc rương là dãy số hòa hòa bi mà sao cho biểu thức sau được nhỏ nhất:
∑
i=1
n
|a i−b i|
Bạn được cung cấp 1 dãy ai hãy giúp công chúa tìm mật khẩu thích hợp
Input:
- Dòng đầu tiên chứa số nguyến n (1 ≤n ≤ 100)
- Dòng tiếp theo chứa n số nguyên a i (1 ≤ a i ≤ 30)
Output:
- Mã khóa là dãy số b sao cho tổng trên là bé nhất Nếu có nhiều câu trả lời in ra bất kì
5
3.2 Ý tưởng
- Bài này ta sử dụng QHĐ bitsmask
- Ta thấy số trong dãy b phù hợp làm mật khẩu lớn nhất tối đa là 60 (2 * a i).
- Có 17 số nguyên tố từ 1đến 60 ta có thể xây dựng được công thức quy hoạch động
- f [i][mask ] mình đã xét đến vị trí ivà đã sử dụng được các số nguyên tố vị trí bit 1 bật trong mask
3.3 Code mẫu
Trang 12#define maxN 100;
int n, a[maxN], f[maxN][1LL << 17], pre_stt maxN[ ], trave[maxN][1LL <
17]
int prime[] 2, 3, , , 5 7 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47,
53, 59}
int main() {
fto i, , n ( 1 ) cin >> a[ ]
*
fto i, , ( 1 60) {
fto j, , ( 0 16) {
if(i prime% [ ] == )
pre_stt[ ] | 1 << j)
}
}
fto i, , n ( 0 ) {
fto j, , ( 0 ( << 17) 1)
f[ ][j oo;
}
f[ ][ ]0 0 ;
fto i, , n ( 1 ) {
fto j, , ( 0 ( << 17) 1) {
fto k, , ( 1 60) {
if((pre_stt[ ] j& )) continue;
if(f i[ ][pre_stt[ ] | j] > f[ - ][j abs( [ ]
k))
trave[ ][pre_stt[ ] | j] = k ;
f[ ][pre_stt[ ] | j] = f[ - ][j + abs( [ ]
k ;
}
}
}
}
int ans oo= ;
int ans_stt;
fto j, , ( 0 ( << 17) 1) {
if(ans> f[ ][j])
ans f= [ ][ ];j ans_stt j = ; }
}
vector<int> res;
for int( i n, mask ans_stt i = = ; >= ; i ) {
res.push_back(trave i[ ][mask]);
mask mask pre_stt= ^ [trave i[ ][mask]];
}
for int( i n = - 1 i >= 0 i ) {
Bitmask và ứng dụng Trang 12
Trang 13cout << res[ ] << " ";
}
*
return 0
}
D KẾT LUẬN
1 Kết quả đạt được
- Hiểu rõ hơn về Bitmask
- Áp dụng Bitmask để giải được một số bài tập cơ bản
2 Khó khăn - Nhược điểm
- Bitmask khá khó để tiếp thu, bản thân mất rất nhiều thời gian để tìm hiểu
- Bitmask khá phức tạp trong việc áp dụng để giải các bài toán
- Tài liệu chủ yếu là tài liệu tiếng anh, gây khó khăn trong qua trình dịch thuật
E TÀI LIỆU THAM KHẢO
Giải thuật và lập trình – Lê Minh Hoàng
Competitive programming 3
Competitive Programmer’s Handbook
https://vnoi.info/
https://vi.wikipedia.org/
https://www.topcoder.com/
http://codeforces.com/
Trang 14Bitmask và ứng dụng Trang 14