IOI 2021 - Ngày 2

Bộ đề bài

# Bài tập Điểm Thời gian: Giới hạn bộ nhớ
1 IOI 2021 - Mutating DNA 100 (p) 1.0s 2G
2 IOI 2021 - Dungeons Game 100 (p) 4.0s 2G
3 IOI 2021 - Bit Shift Registers 100 (p) 1.0s 2G

1. IOI 2021 - Mutating DNA

Điểm: 100 (p) Thời gian: 1.0s Bộ nhớ: 2G Input: bàn phím Output: màn hình

Grace là một nhà sinh học làm việc tại một công ty tin sinh học ở Singapore. Trong công việc của mình, cô phân tích các chuỗi ADN của nhiều sinh vật khác nhau. Một chuỗi ADN được định nghĩa là một xâu gồm các ký tự A, TC. Chú ý rằng trong bài này, các chuỗi ADN không chứa ký tự G.

Một phép đột biến là thao tác hoán đổi hai phần tử của một chuỗi ADN. Ví dụ, một phép đột biến có thể biến ACTA thành AATC bằng cách hoán đổi hai ký tự AC được in đậm.

Khoảng cách đột biến giữa hai chuỗi là số phép đột biến ít nhất cần thực hiện để biến chuỗi này thành chuỗi kia, hoặc bằng \(-1\) nếu không thể biến chuỗi này thành chuỗi kia bằng các phép đột biến.

Grace đang phân tích hai chuỗi ADN \(a\)\(b\), mỗi chuỗi có \(n\) phần tử được đánh chỉ số từ \(0\) đến \(n-1\). Nhiệm vụ của bạn là giúp Grace trả lời \(q\) câu hỏi có dạng: khoảng cách đột biến giữa chuỗi con \(a[x..y]\) và chuỗi con \(b[x..y]\) là bao nhiêu? Ở đây, chuỗi con \(s[x..y]\) của chuỗi ADN \(s\) là dãy các ký tự liên tiếp của \(s\) có chỉ số từ \(x\) đến \(y\), bao gồm cả hai đầu mút. Nói cách khác, \(s[x..y]\) là dãy \(s[x]s[x+1]\ldots s[y]\).

Chi tiết cài đặt

Bạn cần cài đặt các hàm sau:

C++
void init(std::string a, std::string b);
  • a, b: các xâu độ dài \(n\), biểu diễn hai chuỗi ADN cần phân tích.
  • Hàm này được gọi đúng một lần, trước mọi lời gọi tới get_distance.
C++
int get_distance(int x, int y);
  • x, y: chỉ số bắt đầu và kết thúc của các chuỗi con cần phân tích.
  • Hàm cần trả về khoảng cách đột biến giữa \(a[x..y]\)\(b[x..y]\).
  • Hàm này được gọi đúng \(q\) lần.

Dữ liệu vào

Trình chấm mẫu đọc dữ liệu theo định dạng sau:

  • Dòng \(1\): n q.
  • Dòng \(2\): a.
  • Dòng \(3\): b.
  • Dòng \(4+i\) (\(0\le i\le q-1\)): x y cho lời gọi get_distance thứ \(i\).

Dữ liệu ra

Trình chấm mẫu in câu trả lời theo định dạng sau:

  • Dòng \(1+i\) (\(0\le i\le q-1\)): giá trị trả về của lời gọi get_distance thứ \(i\).

Ràng buộc

  • \(1\le n,q\le 100\,000\).
  • \(0\le x\le y\le n-1\).
  • Mỗi ký tự của \(a\)\(b\) là một trong các ký tự A, TC.

Phân nhóm

Nhóm Điểm Ràng buộc bổ sung
1 21 \(y-x\le 2\).
2 22 \(q\le 500\), \(y-x\le 1000\); mỗi ký tự của \(a\)\(b\)A hoặc T.
3 13 Mỗi ký tự của \(a\)\(b\)A hoặc T.
4 28 \(q\le 500\), \(y-x\le 1000\).
5 16 Không có ràng buộc bổ sung.

Ví dụ

Ví dụ 1

Input
6 3
ATACAT
ACTATA
1 3
4 5
3 5
Output
2
1
-1
Note

Xét lời gọi:

init("ATACAT", "ACTATA")

Giả sử trình chấm gọi get_distance(1, 3). Lời gọi này cần trả về khoảng cách đột biến giữa \(a[1..3]\)\(b[1..3]\), tức là hai chuỗi TACCTA. Có thể biến TAC thành CTA bằng \(2\) phép đột biến: \(\mathrm{TAC}\to\mathrm{CAT}\), sau đó \(\mathrm{CAT}\to\mathrm{CTA}\); không thể thực hiện việc biến đổi với ít hơn \(2\) phép đột biến. Vì vậy, lời gọi này cần trả về \(2\).

Giả sử trình chấm gọi get_distance(4, 5). Lời gọi này cần trả về khoảng cách đột biến giữa ATTA. Có thể biến AT thành TA bằng một phép đột biến, và rõ ràng cần ít nhất một phép đột biến. Vì vậy, lời gọi này cần trả về \(1\).

Cuối cùng, giả sử trình chấm gọi get_distance(3, 5). Không thể biến CAT thành ATA bằng bất kỳ dãy phép đột biến nào, nên lời gọi này cần trả về \(-1\).

Nguồn

IOI 2021, Ngày 2 — Mutating DNA (dna).

2. IOI 2021 - Dungeons Game

Điểm: 100 (p) Thời gian: 4.0s Bộ nhớ: 2G Input: bàn phím Output: màn hình

Robert đang thiết kế một trò chơi máy tính mới. Trò chơi gồm một anh hùng, \(n\) đối thủ và \(n+1\) ngục tối. Các đối thủ được đánh số từ \(0\) đến \(n-1\), còn các ngục tối được đánh số từ \(0\) đến \(n\). Đối thủ \(i\) (\(0\le i\le n-1\)) ở trong ngục tối \(i\) và có sức mạnh \(s[i]\). Không có đối thủ nào trong ngục tối \(n\).

Anh hùng bắt đầu bằng việc đi vào ngục tối \(x\) với sức mạnh \(z\). Mỗi khi anh hùng đi vào một ngục tối \(i\) (\(0\le i\le n-1\)), anh hùng đối đầu với đối thủ \(i\) và một trong các tình huống sau xảy ra:

  • Nếu sức mạnh của anh hùng lớn hơn hoặc bằng sức mạnh \(s[i]\) của đối thủ, anh hùng thắng. Sức mạnh của anh hùng tăng thêm \(s[i]\) (\(s[i]\ge 1\)). Sau đó, anh hùng đi vào ngục tối \(w[i]\) (\(w[i]>i\)).
  • Ngược lại, anh hùng thua. Sức mạnh của anh hùng tăng thêm \(p[i]\) (\(p[i]\ge 1\)). Sau đó, anh hùng đi vào ngục tối \(l[i]\).

Lưu ý rằng \(p[i]\) có thể nhỏ hơn, bằng hoặc lớn hơn \(s[i]\). Tương tự, \(l[i]\) có thể nhỏ hơn, bằng hoặc lớn hơn \(i\). Bất kể kết quả đối đầu, đối thủ vẫn ở trong ngục tối \(i\) và giữ nguyên sức mạnh \(s[i]\).

Trò chơi kết thúc khi anh hùng đi vào ngục tối \(n\). Có thể chứng minh rằng trò chơi kết thúc sau một số hữu hạn lần đối đầu, bất kể ngục tối bắt đầu và sức mạnh ban đầu của anh hùng.

Robert nhờ bạn kiểm thử trò chơi bằng cách chạy \(q\) lần mô phỏng. Với mỗi lần mô phỏng, Robert cho ngục tối bắt đầu \(x\) và sức mạnh ban đầu \(z\). Nhiệm vụ của bạn là tìm sức mạnh của anh hùng khi trò chơi kết thúc trong mỗi lần mô phỏng.

Chi tiết cài đặt

Bạn cần cài đặt các hàm sau:

C++
void init(int n, std::vector<int> s, std::vector<int> p,
          std::vector<int> w, std::vector<int> l);
  • n: số đối thủ.
  • s, p, w, l: các mảng độ dài \(n\). Với mỗi \(0\le i\le n-1\):
    • \(s[i]\) là sức mạnh của đối thủ \(i\), cũng là lượng sức mạnh anh hùng nhận thêm sau khi thắng đối thủ \(i\).
    • \(p[i]\) là lượng sức mạnh anh hùng nhận thêm sau khi thua đối thủ \(i\).
    • \(w[i]\) là ngục tối anh hùng đi vào sau khi thắng đối thủ \(i\).
    • \(l[i]\) là ngục tối anh hùng đi vào sau khi thua đối thủ \(i\).
  • Hàm này được gọi đúng một lần, trước mọi lời gọi tới simulate dưới đây.
C++
long long simulate(int x, int z);
  • x: ngục tối đầu tiên anh hùng đi vào.
  • z: sức mạnh ban đầu của anh hùng.
  • Hàm cần trả về sức mạnh của anh hùng khi trò chơi kết thúc, với giả thiết anh hùng bắt đầu bằng việc đi vào ngục tối \(x\) với sức mạnh \(z\).
  • Hàm này được gọi đúng \(q\) lần.

Dữ liệu vào

Trình chấm mẫu đọc dữ liệu theo định dạng sau:

  • Dòng \(1\): n q.
  • Dòng \(2\): \(s[0]\ s[1]\ \ldots\ s[n-1]\).
  • Dòng \(3\): \(p[0]\ p[1]\ \ldots\ p[n-1]\).
  • Dòng \(4\): \(w[0]\ w[1]\ \ldots\ w[n-1]\).
  • Dòng \(5\): \(l[0]\ l[1]\ \ldots\ l[n-1]\).
  • Dòng \(6+i\) (\(0\le i\le q-1\)): x z cho lời gọi simulate thứ \(i\).

Dữ liệu ra

Trình chấm mẫu in câu trả lời theo định dạng sau:

  • Dòng \(1+i\) (\(0\le i\le q-1\)): giá trị trả về của lời gọi simulate thứ \(i\).

Ràng buộc

  • \(1\le n\le 400\,000\).
  • \(1\le q\le 50\,000\).
  • \(1\le s[i],p[i]\le 10^7\) với mọi \(0\le i\le n-1\).
  • \(0\le l[i],w[i]\le n\) với mọi \(0\le i\le n-1\).
  • \(w[i]>i\) với mọi \(0\le i\le n-1\).
  • \(0\le x\le n-1\).
  • \(1\le z\le 10^7\).

Phân nhóm

Nhóm Điểm Ràng buộc bổ sung
1 11 \(n\le 50\,000\), \(q\le 100\), \(s[i],p[i]\le 10\,000\) với mọi \(0\le i\le n-1\).
2 26 \(s[i]=p[i]\) với mọi \(0\le i\le n-1\).
3 13 \(n\le 50\,000\); tất cả đối thủ có cùng sức mạnh, tức là \(s[i]=s[j]\) với mọi \(0\le i,j\le n-1\).
4 12 \(n\le 50\,000\); có nhiều nhất \(5\) giá trị phân biệt trong các giá trị \(s[i]\) với \(0\le i\le n-1\).
5 27 \(n\le 50\,000\).
6 11 Không có ràng buộc bổ sung.

Ví dụ

Ví dụ 1

Input
3 2
2 6 9
3 1 2
2 2 3
1 0 1
0 1
2 3
Output
24
25
Note

Xét lời gọi:

init(3, [2, 6, 9], [3, 1, 2], [2, 2, 3], [1, 0, 1])

Sơ đồ trên minh họa lời gọi này. Mỗi ô vuông biểu diễn một ngục tối. Với các ngục tối \(0\), \(1\)\(2\), các giá trị \(s[i]\)\(p[i]\) được ghi bên trong ô vuông. Các mũi tên màu tím hồng chỉ nơi anh hùng đi tới sau khi thắng một cuộc đối đầu, còn các mũi tên màu đen chỉ nơi anh hùng đi tới sau khi thua.

Giả sử trình chấm gọi simulate(0, 1). Trò chơi diễn ra như sau:

Ngục tối Sức mạnh của anh hùng trước khi đối đầu Kết quả
0 1 Thua
1 4 Thua
0 5 Thắng
2 7 Thua
1 9 Thắng
2 15 Thắng
3 24 Trò chơi kết thúc

Vì vậy, hàm cần trả về \(24\).

Giả sử trình chấm gọi simulate(2, 3). Trò chơi diễn ra như sau:

Ngục tối Sức mạnh của anh hùng trước khi đối đầu Kết quả
2 3 Thua
1 5 Thua
0 6 Thắng
2 8 Thua
1 10 Thắng
2 16 Thắng
3 25 Trò chơi kết thúc

Vì vậy, hàm cần trả về \(25\).

Nguồn

IOI 2021, Ngày 2 — Dungeons Game (dungeons).

3. IOI 2021 - Bit Shift Registers

Điểm: 100 (p) Thời gian: 1.0s Bộ nhớ: 2G Input: bàn phím Output: màn hình

Christopher là một kỹ sư đang làm việc trên một loại bộ vi xử lý mới.

Bộ vi xử lý có thể truy cập \(m\) ô nhớ khác nhau, mỗi ô gồm \(b\) bit (với \(m=100\)\(b=2000\)). Các ô nhớ này được gọi là thanh ghi, được đánh số từ \(0\) đến \(m-1\) và ký hiệu là \(r[0],r[1],\ldots,r[m-1]\). Mỗi thanh ghi là một mảng \(b\) bit, được đánh số từ \(0\) (bit ngoài cùng bên phải) đến \(b-1\) (bit ngoài cùng bên trái). Với mỗi \(i\) (\(0\le i\le m-1\)) và mỗi \(j\) (\(0\le j\le b-1\)), ký hiệu bit thứ \(j\) của thanh ghi \(i\)\(r[i][j]\).

Với một dãy bit \(d_0,d_1,\ldots,d_{\ell-1}\) có độ dài \(\ell\) bất kỳ, giá trị số nguyên của dãy bằng:

\[ 2^0\cdot d_0+2^1\cdot d_1+\cdots+2^{\ell-1}\cdot d_{\ell-1}. \]

Giá trị số nguyên được lưu trong thanh ghi \(i\) là giá trị số nguyên của dãy bit của thanh ghi đó, tức là:

\[ 2^0\cdot r[i][0]+2^1\cdot r[i][1]+\cdots+2^{b-1}\cdot r[i][b-1]. \]

Bộ vi xử lý có \(9\) loại lệnh dùng để thay đổi các bit trong các thanh ghi. Mỗi lệnh thao tác trên một hoặc nhiều thanh ghi và lưu kết quả vào một thanh ghi. Sau đây, ký hiệu \(x:=y\) chỉ thao tác thay đổi giá trị của \(x\) thành \(y\). Các loại lệnh được mô tả như sau:

  • move(t, y): sao chép mảng bit trong thanh ghi \(y\) sang thanh ghi \(t\). Với mỗi \(j\) (\(0\le j\le b-1\)), gán \(r[t][j]:=r[y][j]\).
  • store(t, v): đặt thanh ghi \(t\) bằng \(v\), trong đó \(v\) là một mảng \(b\) bit. Với mỗi \(j\) (\(0\le j\le b-1\)), gán \(r[t][j]:=v[j]\).
  • and(t, x, y): thực hiện phép AND theo từng bit giữa thanh ghi \(x\) và thanh ghi \(y\), rồi lưu kết quả vào thanh ghi \(t\). Với mỗi \(j\) (\(0\le j\le b-1\)), gán \(r[t][j]:=1\) nếu cả \(r[x][j]\)\(r[y][j]\) đều bằng \(1\); ngược lại, gán \(r[t][j]:=0\).
  • or(t, x, y): thực hiện phép OR theo từng bit giữa thanh ghi \(x\) và thanh ghi \(y\), rồi lưu kết quả vào thanh ghi \(t\). Với mỗi \(j\) (\(0\le j\le b-1\)), gán \(r[t][j]:=1\) nếu ít nhất một trong hai bit \(r[x][j]\)\(r[y][j]\) bằng \(1\); ngược lại, gán \(r[t][j]:=0\).
  • xor(t, x, y): thực hiện phép XOR theo từng bit giữa thanh ghi \(x\) và thanh ghi \(y\), rồi lưu kết quả vào thanh ghi \(t\). Với mỗi \(j\) (\(0\le j\le b-1\)), gán \(r[t][j]:=1\) nếu đúng một trong hai bit \(r[x][j]\)\(r[y][j]\) bằng \(1\); ngược lại, gán \(r[t][j]:=0\).
  • not(t, x): thực hiện phép NOT theo từng bit của thanh ghi \(x\), rồi lưu kết quả vào thanh ghi \(t\). Với mỗi \(j\) (\(0\le j\le b-1\)), gán \(r[t][j]:=1-r[x][j]\).
  • left(t, x, p): dịch tất cả các bit của thanh ghi \(x\) sang trái \(p\) vị trí và lưu kết quả vào thanh ghi \(t\). Kết quả dịch là một mảng \(v\) gồm \(b\) bit. Với mỗi \(j\) (\(0\le j\le b-1\)), \(v[j]=r[x][j-p]\) nếu \(j\ge p\), và \(v[j]=0\) trong trường hợp còn lại. Với mỗi \(j\) (\(0\le j\le b-1\)), gán \(r[t][j]:=v[j]\).
  • right(t, x, p): dịch tất cả các bit của thanh ghi \(x\) sang phải \(p\) vị trí và lưu kết quả vào thanh ghi \(t\). Kết quả dịch là một mảng \(v\) gồm \(b\) bit. Với mỗi \(j\) (\(0\le j\le b-1\)), \(v[j]=r[x][j+p]\) nếu \(j\le b-1-p\), và \(v[j]=0\) trong trường hợp còn lại. Với mỗi \(j\) (\(0\le j\le b-1\)), gán \(r[t][j]:=v[j]\).
  • add(t, x, y): cộng giá trị số nguyên trong thanh ghi \(x\) và thanh ghi \(y\), rồi lưu kết quả vào thanh ghi \(t\). Phép cộng được thực hiện theo modulo \(2^b\). Cụ thể, gọi \(X\)\(Y\) lần lượt là các giá trị số nguyên trong thanh ghi \(x\) và thanh ghi \(y\) trước thao tác, và \(T\) là giá trị số nguyên trong thanh ghi \(t\) sau thao tác. Nếu \(X+Y<2^b\), đặt các bit của \(t\) sao cho \(T=X+Y\). Ngược lại, đặt các bit của \(t\) sao cho \(T=X+Y-2^b\).

Christopher muốn bạn giải hai loại bài toán bằng bộ vi xử lý mới. Loại bài toán được biểu thị bằng số nguyên \(s\). Với cả hai loại, bạn cần tạo một chương trình, tức là một dãy các lệnh được định nghĩa ở trên.

Dữ liệu đầu vào của chương trình gồm \(n\) số nguyên \(a[0],a[1],\ldots,a[n-1]\), mỗi số có \(k\) bit, tức là \(a[i]<2^k\) (\(0\le i\le n-1\)). Trước khi chương trình được thực thi, tất cả các số đầu vào được lưu liên tiếp trong thanh ghi \(0\), sao cho với mỗi \(i\) (\(0\le i\le n-1\)), giá trị số nguyên của dãy \(k\) bit \(r[0][i\cdot k],r[0][i\cdot k+1],\ldots,r[0][(i+1)\cdot k-1]\) bằng \(a[i]\). Lưu ý rằng \(n\cdot k\le b\). Tất cả các bit còn lại trong thanh ghi \(0\) (có chỉ số từ \(n\cdot k\) đến \(b-1\), bao gồm cả hai đầu mút) và tất cả các bit trong các thanh ghi khác đều được khởi tạo bằng \(0\).

Chạy một chương trình nghĩa là thực thi các lệnh của nó theo thứ tự. Sau khi thực thi lệnh cuối cùng, đầu ra của chương trình được tính từ giá trị cuối cùng của các bit trong thanh ghi \(0\). Cụ thể, đầu ra là một dãy \(n\) số nguyên \(c[0],c[1],\ldots,c[n-1]\), trong đó với mỗi \(i\) (\(0\le i\le n-1\)), \(c[i]\) là giá trị số nguyên của dãy bit từ vị trí \(i\cdot k\) đến \((i+1)\cdot k-1\) trong thanh ghi \(0\). Sau khi chạy chương trình, các bit còn lại của thanh ghi \(0\) (có chỉ số ít nhất là \(n\cdot k\)) và tất cả các bit trong các thanh ghi khác có thể có giá trị bất kỳ.

  • Loại bài toán thứ nhất (\(s=0\)) là tìm số nguyên nhỏ nhất trong các số đầu vào \(a[0],a[1],\ldots,a[n-1]\). Cụ thể, \(c[0]\) phải bằng giá trị nhỏ nhất của \(a[0],a[1],\ldots,a[n-1]\). Các giá trị \(c[1],c[2],\ldots,c[n-1]\) có thể tùy ý.
  • Loại bài toán thứ hai (\(s=1\)) là sắp xếp các số nguyên đầu vào \(a[0],a[1],\ldots,a[n-1]\) theo thứ tự không giảm. Cụ thể, với mỗi \(i\) (\(0\le i\le n-1\)), \(c[i]\) phải bằng số nguyên nhỏ thứ \(1+i\) trong \(a[0],a[1],\ldots,a[n-1]\) (tức là \(c[0]\) là số nguyên nhỏ nhất trong các số đầu vào).

Hãy cung cấp cho Christopher các chương trình giải được những bài toán này, mỗi chương trình gồm không quá \(q\) lệnh.

Chi tiết cài đặt

Bạn cần cài đặt hàm sau:

C++
void construct_instructions(int s, int n, int k, int q);
  • s: loại bài toán.
  • n: số lượng số nguyên đầu vào.
  • k: số bit của mỗi số nguyên đầu vào.
  • q: số lệnh tối đa được phép sử dụng.
  • Hàm được gọi đúng một lần và cần xây dựng một dãy lệnh thực hiện yêu cầu của bài toán.

Hàm này cần gọi một hoặc nhiều hàm dưới đây để xây dựng dãy lệnh:

C++
void append_move(int t, int x);
void append_store(int t, std::vector<bool> v);
void append_and(int t, int x, int y);
void append_or(int t, int x, int y);
void append_xor(int t, int x, int y);
void append_not(int t, int x);
void append_left(int t, int x, int s);
void append_right(int t, int x, int s);
void append_add(int t, int x, int y);
  • Các hàm lần lượt thêm lệnh move(t, y), store(t, v), and(t, x, y), or(t, x, y), xor(t, x, y), not(t, x), left(t, x, p), right(t, x, p) hoặc add(t, x, y) tương ứng vào cuối chương trình.
  • Với mọi lệnh có các tham số tương ứng, \(t,x,y\) phải nằm trong đoạn từ \(0\) đến \(m-1\).
  • Các chỉ số \(t,x,y\) không nhất thiết phải đôi một khác nhau.
  • Với các lệnh leftright, \(p\) phải nằm trong đoạn từ \(0\) đến \(b\), bao gồm cả hai đầu mút.
  • Với lệnh store, độ dài của \(v\) phải bằng \(b\).

Bạn cũng có thể gọi hàm sau để hỗ trợ kiểm thử lời giải:

C++
void append_print(int t);
  • Mọi lời gọi hàm này đều bị bỏ qua khi chấm bài.
  • Trong trình chấm mẫu, hàm thêm một thao tác print(t) vào cuối chương trình.
  • Khi gặp thao tác print(t) trong quá trình thực thi chương trình, trình chấm mẫu in \(n\) số nguyên \(k\) bit được tạo bởi \(n\cdot k\) bit đầu tiên của thanh ghi \(t\) (xem định dạng ở phần Dữ liệu ra).
  • \(t\) phải thỏa mãn \(0\le t\le m-1\).
  • Lời gọi hàm này không làm tăng số lệnh đã xây dựng.

Sau khi thêm lệnh cuối cùng, hàm construct_instructions cần kết thúc. Chương trình sau đó được đánh giá trên một số bộ dữ liệu, mỗi bộ gồm \(n\) số nguyên \(k\) bit \(a[0],a[1],\ldots,a[n-1]\). Lời giải vượt qua một bộ dữ liệu nếu đầu ra \(c[0],c[1],\ldots,c[n-1]\) của chương trình trên đầu vào đó thỏa mãn:

  • Nếu \(s=0\), \(c[0]\) phải là giá trị nhỏ nhất trong \(a[0],a[1],\ldots,a[n-1]\).
  • Nếu \(s=1\), với mỗi \(i\) (\(0\le i\le n-1\)), \(c[i]\) phải bằng số nguyên nhỏ thứ \(1+i\) trong \(a[0],a[1],\ldots,a[n-1]\).

Quá trình chấm có thể đưa ra một trong các thông báo lỗi sau:

  • Invalid index: một chỉ số thanh ghi không hợp lệ (có thể âm) được truyền làm tham số t, x hoặc y trong một lời gọi hàm.
  • Value to store is not b bits long: độ dài của v truyền cho append_store không bằng \(b\).
  • Invalid shift value: giá trị p truyền cho append_left hoặc append_right không nằm trong đoạn từ \(0\) đến \(b\), bao gồm cả hai đầu mút.
  • Too many instructions: hàm của bạn đã cố thêm quá \(q\) lệnh.

Dữ liệu vào

Trình chấm mẫu đọc dòng đầu tiên theo định dạng:

s n k q

Tiếp theo là một số dòng, mỗi dòng mô tả một bộ dữ liệu theo định dạng:

a[0] a[1] … a[n − 1]

Mỗi bộ dữ liệu gồm \(n\) số nguyên đầu vào \(a[0],a[1],\ldots,a[n-1]\). Sau tất cả các bộ dữ liệu là một dòng chỉ chứa số \(-1\).

Dữ liệu ra

Đầu tiên, trình chấm mẫu gọi construct_instructions(s, n, k, q). Nếu lời gọi vi phạm một ràng buộc trong đề bài, trình chấm mẫu in một trong các thông báo lỗi ở cuối phần Chi tiết cài đặt rồi kết thúc. Ngược lại, trình chấm mẫu trước tiên in từng lệnh do construct_instructions(s, n, k, q) thêm vào, theo đúng thứ tự. Với lệnh store, \(v\) được in theo thứ tự chỉ số từ \(0\) đến \(b-1\).

Sau đó, trình chấm mẫu xử lý các bộ dữ liệu theo thứ tự. Với mỗi bộ dữ liệu, trình chấm chạy chương trình đã xây dựng trên đầu vào của bộ dữ liệu đó.

Với mỗi thao tác print(t), gọi \(d[0],d[1],\ldots,d[n-1]\) là dãy số nguyên sao cho với mỗi \(i\) (\(0\le i\le n-1\)), \(d[i]\) là giá trị số nguyên của dãy bit từ \(i\cdot k\) đến \((i+1)\cdot k-1\) trong thanh ghi \(t\) tại thời điểm thực hiện thao tác. Trình chấm in dãy này theo định dạng:

register t: d[0] d[1] … d[n − 1]

Sau khi thực thi tất cả các lệnh, trình chấm mẫu in đầu ra của chương trình. Nếu \(s=0\), đầu ra cho mỗi bộ dữ liệu có định dạng:

c[0]

Nếu \(s=1\), đầu ra cho mỗi bộ dữ liệu có định dạng:

c[0] c[1] … c[n − 1]

Sau khi thực thi tất cả các bộ dữ liệu, trình chấm in:

number of instructions: X

Trong đó \(X\) là số lệnh trong chương trình của bạn.

Ràng buộc

  • \(m=100\).
  • \(b=2000\).
  • \(0\le s\le 1\).
  • \(2\le n\le 100\).
  • \(1\le k\le 10\).
  • \(q\le 4000\).
  • \(0\le a[i]\le 2^k-1\) với mọi \(0\le i\le n-1\).

Phân nhóm

Nhóm Điểm Ràng buộc bổ sung
1 10 \(s=0\), \(n=2\), \(k\le 2\), \(q=1000\).
2 11 \(s=0\), \(n=2\), \(k\le 2\), \(q=20\).
3 12 \(s=0\), \(q=4000\).
4 25 \(s=0\), \(q=150\).
5 13 \(s=1\), \(n\le 10\), \(q=4000\).
6 29 \(s=1\), \(q=4000\).

Ví dụ

Ví dụ 1

Giải thích

Giả sử \(s=0\), \(n=2\), \(k=1\), \(q=1000\). Có hai số nguyên đầu vào \(a[0]\)\(a[1]\), mỗi số có \(k=1\) bit. Trước khi chương trình được thực thi, \(r[0][0]=a[0]\)\(r[0][1]=a[1]\). Tất cả các bit khác trong bộ vi xử lý đều bằng \(0\). Sau khi thực thi tất cả các lệnh, cần có \(c[0]=r[0][0]=\min(a[0],a[1])\), là giá trị nhỏ nhất của \(a[0]\)\(a[1]\).

Chỉ có \(4\) đầu vào có thể có:

  • Trường hợp \(1\): \(a[0]=0\), \(a[1]=0\).
  • Trường hợp \(2\): \(a[0]=0\), \(a[1]=1\).
  • Trường hợp \(3\): \(a[0]=1\), \(a[1]=0\).
  • Trường hợp \(4\): \(a[0]=1\), \(a[1]=1\).

Trong cả \(4\) trường hợp, \(\min(a[0],a[1])\) bằng phép AND theo từng bit giữa \(a[0]\)\(a[1]\). Vì vậy, có thể xây dựng chương trình bằng các lời gọi sau:

  1. append_move(1, 0): thêm lệnh sao chép \(r[0]\) sang \(r[1]\).
  2. append_right(1, 1, 1): thêm lệnh dịch tất cả các bit trong \(r[1]\) sang phải \(1\) bit, rồi lưu kết quả trở lại \(r[1]\). Vì mỗi số nguyên có độ dài \(1\) bit, sau lệnh này \(r[1][0]\) bằng \(a[1]\).
  3. append_and(0, 0, 1): thêm lệnh lấy phép AND theo từng bit giữa \(r[0]\)\(r[1]\), rồi lưu kết quả vào \(r[0]\). Sau lệnh này, \(r[0][0]\) được gán bằng phép AND theo từng bit giữa \(r[0][0]\)\(r[1][0]\), tức là phép AND theo từng bit giữa \(a[0]\)\(a[1]\), đúng như yêu cầu.

Ví dụ 2

Giải thích

Giả sử \(s=1\), \(n=2\), \(k=1\), \(q=1000\). Như ví dụ trước, chỉ có \(4\) đầu vào có thể có. Trong cả \(4\) trường hợp, \(\min(a[0],a[1])\) là phép AND theo từng bit giữa \(a[0]\)\(a[1]\), còn \(\max(a[0],a[1])\) là phép OR theo từng bit giữa \(a[0]\)\(a[1]\). Có thể thực hiện các lời gọi sau, theo thứ tự:

append_move(1,0)
append_right(1,1,1)
append_and(2,0,1)
append_or(3,0,1)
append_left(3,3,1)
append_or(0,2,3)

Sau khi thực thi các lệnh này, \(c[0]=r[0][0]\) chứa \(\min(a[0],a[1])\), còn \(c[1]=r[0][1]\) chứa \(\max(a[0],a[1])\), nên đầu vào đã được sắp xếp.

Nguồn

IOI 2021, Ngày 2 — Bit Shift Registers (registers).