ĐÁP ÁN MÔN TIN HỌCĐề giới thiệu DHBB 2022 Khối 10 - Tính Hải Dương Tác giả: Lê Thanh Bình: Đơn vị: Trường THPT Chuyên Nguyễn Trãi - Hải Dương Điện thoại: 0982 199 329 Bài 1: SGAME Bài to
Trang 1ĐÁP ÁN MÔN TIN HỌC
Đề giới thiệu DHBB 2022 Khối 10 - Tính Hải Dương
Tác giả: Lê Thanh Bình:
Đơn vị: Trường THPT Chuyên Nguyễn Trãi - Hải Dương
Điện thoại: 0982 199 329
Bài 1: SGAME
Bài toán được giải bằng phương pháp qui hoạch động: Đặt nếu như có phương án cho người đi trước chiến thắng khi chọn bi trong đoạn với giả thiết trước đó người này đã
có viên bi.
Đặt là tổng số viên bi màu đỏ là số lượng viên bi màu đỏ nằm trong đoạn Mảng có thể được chuẩn bị trước bằng kỹ thuật tổng tiền tố.
Đặt ta có là số viên bi đỏ của người chơi sau hiện có.
Do vậy:
• nếu
• nếu
• với các trường hợp còn lại
Đáp số là dp[1,n,0]
Chương trình:
#include <cstdio>
using namespace std;
const int MAXN = 352;
int n,k;
char str[MAXN];
int prefix[MAXN];
int dp[MAXN][MAXN][MAXN];
int can_win(int l, int r, int uk){
int& res = dp[l][r][uk];
if(res == -1){
int total_red = prefix[n-1];
int red_left = prefix[r];
if(l) red_left -= prefix[l-1];
int other_red = total_red - red_left - uk;
if(uk>=k){
res = 0;
}else if(other_red>=k){
res = 1;
Trang 2}else{
if(!can_win(l+1, r, other_red) || !can_win(l, r-1, other_red)){
res = 1;
}else{
res = 0;
}
}
}
return res;
}
void solve() {
for(int i=0;i<MAXN;i++) for(int j=0;j<MAXN;j++) for(int k=0;k<MAXN;k++)
dp[i][j][k] = -1;
scanf("%d%d", &n, &k);
scanf("%s", str);
prefix[0] = (str[0] == 'R');
for(int i=1;i<n;i++) prefix[i] = prefix[i-1] + (str[i] == 'R');
if(can_win(0, n-1, 0)){
puts("YES");
}else{
puts("NO");
}
}
int main(){
freopen("SGAME.inp","r",stdin);
freopen("SGAME.out","w",stdout);
int T; scanf("%d", &T);
while (T ) solve();
}
Bài 2: SADJ
Gọi các đỉnh được lựa chọn là các đỉnh màu đen, các đỉnh còn lại là các đỉnh màu trắng Chú ý rằng mỗi đỉnh màu đen có đúng một đỉnh kề màu đen và nó cũng là đỉnh
kề màu đen của đỉnh này Do vậy các đỉnh màu đen đi thành từng cặp và là hai đỉnh của một cạnh nào đó.
Subtask 1: Trong trường hợp này đồ thị có dạng một vòng tròn Dễ thấy trên vòng
tròn này hai nút liên tiếp màu đen phải xen kẽ với hai nút liên tiếp màu trắng Do vậy bài toán chỉ có nghiệm khi chia hết cho 4 và đáp số là
Subtask 2: Đơn giản là thử hết tất cả cách tô đỉnh màu đen, sau đó kiểm tra Độ phức
tạp thuật toán
Subtask 3, 4: Nhận xét rằng đồ thị chứa đúng một chu trình, các đỉnh còn lại được gắn
vào chu trình này dưới dạng hình cây Dùng DFS ta có thể tìm được chu trình này.
Trang 3Chọn một cạnh trên chu trình và lấy một đỉnh của cạnh làm đỉnh gốc, đỉnh còn lại là đỉnh đặc biệt Có 4 khả năng tô màu cho hai đỉnh này Bỏ cạnh vừa chọn ra khỏi đồ thị
ta có một cây Bài toán được thực hiện bằng cách qui hoạch động trên cây.
Đặt là số đỉnh đen nhỏ nhất có trong cây con gốc với màu của đỉnh là , màu của đỉnh cha là , màu của đỉnh gốc là và màu của đỉnh đặc biệt là Trước tiên ta xác định các trường hợp không phù hợp (kết quả trả ngay -1) Đó là:
• là gốc và
• là đỉnh đặc biệt và
• là đỉnh đặc biệt, (đen) và kề với hai đỉnh đen
Việc qui hoạch động được tính từ con lên gốc.
Chương trình:
#include<bits/stdc++.h>
using namespace std;
typedef long long llint;
typedef pair <int, int> pi;
const int MAXN = 100005;
const int INF = 1000000007;
int n, root, special;
int head[MAXN], par[MAXN];
int dp[MAXN][2][2][2][2];
vector <int> v[MAXN];
int nadi (int x) {
if (x == head[x]) return x;
return head[x] = nadi(head[x]);
}
void dfs (int x, int rod) {
par[x] = rod;
for (auto sus : v[x]) {
if (sus == rod) continue;
dfs(sus, x);
}
}
int calc (int x, int me, int up, int rt, int sp) {
if (dp[x][me][up][rt][sp] != -1) return dp[x][me][up][rt][sp];
llint res = INF;
bool ok = 1;
Trang 4if (x == root && me != rt) ok = 0;
if (x == special && me != sp) ok = 0;
if (x == special && up && rt) ok = 0;
if (!ok) return dp[x][me][up][rt][sp] = INF;
bool covered = 0;
if (up) covered = 1;
if (x == root && sp) covered = 1;
if (x == special && rt) covered = 1;
llint sum = me;
for (auto sus : v[x]) {
if (sus == par[x]) continue;
sum += calc(sus, 0, me, rt, sp);
}
if (covered) {
res = min(res, sum);
} else {
for (auto sus : v[x]) {
if (sus == par[x]) continue;
llint val = sum - calc(sus, 0, me, rt, sp) + calc(sus, 1, me, rt, sp); res = min(res, val);
}
}
return dp[x][me][up][rt][sp] = res;
}
void solve () {
int sol = INF;
for (int rt = 0; rt <= 1; rt++) {
for (int sp = 0; sp <= 1; sp++) {
sol = min(sol, calc(root, rt, 0, rt, sp));
}
}
if (sol == INF) cout << -1; else cout << sol;
}
int main () {
freopen("SADJ.inp","r",stdin);
freopen("SADJ.out","w",stdout);
ios_base::sync_with_stdio(false);
cin.tie(0);
memset(dp, -1, sizeof dp);
cin >> n;
for (int i = 1; i <= n; i++) {
head[i] = i;
Trang 5}
for (int i = 0; i < n; i++) {
int a, b;
cin >> a >> b;
int pa = nadi(a);
int pb = nadi(b);
if (pa == pb) {
root = a;
special = b;
} else {
head[pa] = pb;
v[a].push_back(b);
v[b].push_back(a);
}
}
dfs(root, 0);
solve();
return 0;
}
Bài 3: PARTITION
Thuật toán đơn giản để trả lời cho truy vấn là bắt đầu từ thêm dần các đoạn thẳng chừng nào đoạn thẳng thêm không giao với các đoạn thẳng đã có Quá trình này lại tiếp tục với phần còn lại của truy vấn, cho đến khi không còn đoạn nào trong truy vấn nữa.
Đặt là chỉ số tiếp theo của chỉ số trong đó các đoạn không giao nhau Giả sử xây dựng được mảng khi đó trả lời truy vấn có thể thực hiện đơn giản như sau:
res=0;
x=u;
while (x<=v) ++res, x=nxt[x];
Bài toán còn lại cần giải quyết hiệu quả việc tính mảng và tăng tốc đoạn chương trình trên (đoạn chương trình trên có thời gian thực hiện là ).
1) Tính mảng một cách hiệu quả
Nhận xét rằng nếu thì do vậy ta có thể sử dụng kỹ thuật "hai con trỏ" để tăng hiệu quả tính toán.Với một đoạn ta chỉ cần quan tâm đến hai đoạn: đoạn có đầu mút trái nhỏ nhất và đoạn có đầu mút trái lớn nhất
map<int,int> nho;
j=1;
for(i=1;i<=n;++i) {
while (j<=n) {
if (nho.find(a[j])!=nho.end()) break;
auto it=nho.upper_bound(a[j]);
if (it!=nho.end() && (it->first)<=b[j]) break;
it;
if (it!=nho.rend() && (it->second)>=a[j]) break;
nho[a[j]]=b[j]; ++j;
Trang 6};
nxt[i]=j;
}
2) Xử lý đoạn code cơ bản hiệu quả
Ta sẽ sử dụng kỹ thuật bảng thưa (sparse table) bằng cách đặt:
f[i,0]=nxt[i], f[i,1]=f[nxt[i],0], , f[i,k]=f[f[i,k-1],k-1],
Thời gian thực hiện mỗi truy vấn giảm xuống còn
Chương trình:
#include<bits/stdc++.h>
using namespace std;
typedef long long llint;
typedef pair <int, int> pi;
const int MAXN = 200005;
const int LOG = 18;
int n, q;
int lef[MAXN], rig[MAXN];
int par[MAXN][LOG];
set <pi> st;
bool can_insert (int ind) {
if (st.empty()) return 1;
auto it = st.lower_bound({lef[ind], 0});
int nxt = it -> second;
if (lef[ind] <= lef[nxt] && lef[nxt] <= rig[ind]) return 0;
if (it != st.begin()) {
it ;
int prv = it -> second;
if (lef[prv] <= lef[ind] && lef[ind] <= rig[prv]) return 0;
}
return 1;
}
void precompute () {
int lim = 0;
for (int i = 1; i <= n; i++) {
if (lim < i) {
st.clear();
lim = i;
st.insert({lef[i], i});
Trang 7}
while (lim + 1 <= n && can_insert(lim + 1)) {
lim++;
st.insert({lef[lim], lim});
}
par[i][0] = lim + 1;
st.erase({lef[i], i});
}
par[n + 1][0] = n + 1;
for (int i = 1; i < LOG; i++) {
for (int j = 1; j <= n + 1; j++) {
par[j][i] = par[par[j][i - 1]][i - 1];
}
}
}
int upit (int a, int b) {
int res = 0;
for (int i = LOG - 1; i >= 0; i ) {
if (par[a][i] <= b) {
a = par[a][i];
res += 1 << i;
}
}
return res + 1;
}
int main () {
freopen("PARTITION.inp","r",stdin);
freopen("PARTITION.out","w",stdout);
ios_base::sync_with_stdio(false);
cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> lef[i] >> rig[i];
}
precompute();
cin >> q;
for (int i = 1; i <= q; i++) {
int a, b;
cin >> a >> b;
cout << upit(a, b) << '\n';
}
return 0;
}