Sáng tạo trong thuật toán và lập trình với ngôn ngữ Pascal và C# - Tập 1 - Chương 1: Giải một bài toán tin

Phần này sẽ giới thiệu một số bước thường vận dụng trong quá trình giải các bài

toán tin.

1. Bước đầu tiên và là bước quan trọng nhất là hiểu rõ nội dung bài toán.

Đây là yêu cầu quen thuộc đối với những người làm toán. Để hiểu bài toán theo

cách tiếp cận của tin học ta phải gắng xây dựng một số thí dụ phản ánh đúng các

yêu cầu đề ra của đầu bài rồi thử giải các thí dụ đó để hình thành dần những hướng

đi của thuật toán.

2. Bước thứ hai là dùng một ngôn ngữ quen thuộc, tốt nhất là ngôn ngữ toán học đặc

tả các đối tượng cần xử lí ở mức độ trừu tượng, lập các tương quan, xây dựng các

hệ thức thể hiện các quan hệ giữa các đại lượng cần xử lí.

3. Bước thứ ba là xác định cấu trúc dữ liệu để biểu diễn các đối tượng cần xử lí cho

phù hợp với các thao tác của thuật toán.

Trong những bước tiếp theo ta tiếp tục làm mịn dần các đặc tả theo trình tự từ trên

xuống, từ trừu tượng đến cụ thể, từ đại thể đến chi tiết.

4. Bước cuối cùng là sử dụng ngôn ngữ lập trình đã chọn để viết chương trình hoàn

chỉnh. Ở bước này ta tiến hành theo kĩ thuật đi từ dưới lên, từ những thao tác nhỏ

đến các thao tác tổ hợp.

pdf24 trang | Chuyên mục: Pascal | Chia sẻ: dkS00TYs | Lượt xem: 2591 | Lượt tải: 1download
Tóm tắt nội dung Sáng tạo trong thuật toán và lập trình với ngôn ngữ Pascal và C# - Tập 1 - Chương 1: Giải một bài toán tin, để xem tài liệu hoàn chỉnh bạn click vào nút "TẢI VỀ" ở trên
i := 1 to n do a[i]:=random(m); 
 exit; 
end; 
 { Khoi tri mang co tong d phan tu dau 
 bang tong cac phan tu con lai } 
 d := random(n div 2)+ 1; { diem chia } 
 t := 0; 
 for i := 1 to d do 
 begin 
 a[i] := random(n); 
 t := t + a[i]; 
 end; { t = sum(a[1..d]) } 
 for i := d+1 to n-1 do 
 begin { sum(a[d+1..i]) + t = sum(a[1..d]) } 
 a[i] := random(t); 
 t := t-a[i]; 
 end; 
 a[n] := t; { sum(a[1..d]) = sum(a[d+1..n]) } 
end; 
procedure Xem: Hiển thị mảng a, tự viết 
function Chia: integer; 
var i, t, t2, tr: integer; 
begin 
 Chia := -1; t := 0; 
 for i:=1 to n do t:=t+a[i]; {t=sum(a[1..n]} 
 if Odd(t) then exit; { vo nghiem } 
 t2 := t div 2; tr := 0; 
 for i:=1 to n do 
 begin 
Sáng tạo trong Thuật toán và Lập trình Tập I 
19 
 tr := tr + a[i]; 
 if tr > t2 then exit; {vo nghiem } 
 if tr = t2 then { co nghiem i } 
 begin Chia:= i; exit; end; 
 end; 
end; 
procedure Test; 
var i: integer; 
begin 
 repeat 
 Gen(10); Xem; i := Chia; 
 if i = -1 then writeln('Khong chia duoc') 
 else 
 begin 
 writeln('Doan thu nhat: a[1..',i,']'); 
 writeln('Doan thu hai: a[',i+1,'..',n,']'); 
 end; 
 until ReadKey=Esc; 
end; 
BEGIN 
 Test; 
END. 
Chú ý 
1. Muốn dừng chương trình hãy nhấn phím Esc có mã ASCII là #27. 
 2. Nếu mảng a có chứa một số giá trị 0 thì bài toán có thể có nhiều nghiệm 
(nhiều cách chia). 
// C# 
using System; 
namespace SangTao1 
{ 
 class ChiaMangTiLe1_1 
 { 
 static void Main() 
 { 
 do { 
 Run(20); 
 Console.Write("\n Bam phim ENTER “ + 
 “de tiep tuc, "); 
 Console.Write("\n Bam phim T de thoat: "); 
 } while (Console.ReadLine() == ""); 
 } 
 static public void Run(int n) 
 { int[] a = new int[n]; 
 Gen(a, n); // sinh ngau nhien 1 test 
 Print(a, n); 
 int t = 0, d = Chia(a, n, ref t); 
 if (d < 0) 
 Console.WriteLine("\n Khong chia duoc"); 
 else if (KiemTra(a, n, d)) 
{ Console.WriteLine("\n Doan thu nhat: 1..{0} ",d); 
 Console.WriteLine("\n Doan thu hai: {0}..{1} ", 
Sáng tạo trong Thuật toán và Lập trình Tập I 
20 
 d+1, n); 
 Console.WriteLine("\n Tong moi doan: " + t); 
 } 
else Console.WriteLine("\n Loi giai sai!"); 
 } // end Run 
// Kiem tra sum(a[1..d] == sum(a[d+1..n]) ? 
static public bool KiemTra(int[] a, int n, int d) 
{ if (d = n) return false; 
 int t = 0; 
 for (int i = 0; i < d; ++i) t += a[i]; 
 for (int i = d; i < n; ++i) t -= a[i]; 
 return (t == 0) ? true : false; 
} 
static public int Chia(int[] a, int n, ref int t) 
{ int sum = 0; // sum = tong(a[1..n]) 
 for (int i = 0; i < n; ++i) sum += a[i]; 
 if (sum % 2 != 0) return -1; 
 t = sum / 2; // tong moi doan 
 int tr = 0; // tong rieng 
 // doan 1: tr = sum a[1..i] 
 for (int i = 0; i < n; ++i) 
 { tr += a[i]; 
 if (tr == t) return i+1; 
 } 
 return -1; 
 } 
 // sinh ngau nhien n so ghi vao mang a 
 static public void Gen(int[] a, int n) 
 { 
 Random r = new Random(); 
 if (r.Next(2) == 0) 
 { // 1/2 so test la vo nghiem 
 for (int i = 0; i < n; ++i) a[i]=r.Next(n); 
 return; 
 } 
 // sinh mang a: sum(a[0..d-1])=sum(a[d..n-1]) 
 int d = r.Next(n / 2) + 1; // diem chia 
 int t = 0; 
 // sinh doan a[0..d-1] 
 for (int i = 0; i < d; ++i) 
 { a[i] = r.Next(n); t += a[i]; } 
 // sinh tiep doan a[d..n-1] 
 int n1 = n-1; 
 for (int i = d; i < n1; ++i) 
 { a[i] = r.Next(t); t -= a[i]; } 
 a[n-1] = t; // phan tu cuoi 
 } 
 static public void Print(int[] a, int n): tự viết 
 } // SoCapNhan 
} // SangTao1 
Sáng tạo trong Thuật toán và Lập trình Tập I 
21 
Bài 1.6. Chia mảng tỉ lệ 1:k 
 Tìm cách chia dãy số nguyên không âm a1, a2,...,an, n > 1 cho trước thành hai 
đoạn có tổng các phần tử trong một đoạn gấp k lần tổng các phần tử trong 
đoạn kia, k nguyên dương. 
Đặc tả 
Gọi t là tổng các phần tử của dãy a, t = sum(a[1..n]) 
Muốn chia a thành hai đoạn a[1..i] và a[i + 1..n] có tổng gấp nhau k lần ta phải có: 
1. t chia hết cho (k + 1). Đặt t1 = t div (k + 1) và tk = t - t1. 
2. (#E i: 1 <= i <= n): sum(a[1..i]) = t1 hoặc sum(a[1..i]) = tk. 
Để ý rằng nếu k = 1 thì t1 = tk; nếu k > 1 thì t1 < tk, do đó bài này là trường hợp 
riêng của bài trước khi k = 1. 
Trong chương trình dưới đây, hàm Chia(k) cho giá trị i nếu mảng a chia được 
thành hai đoạn a[1..i] và a[(i + 1)..n] có tổng gấp k lần nhau. Trong trường hợp vô 
nghiệm Chia = -1. Ta gọi i là điểm chia và dùng biến tr (tổng riêng) để tích luỹ 
tổng các phần tử của đoạn đang xét a[1..i]. Khi tr = t1 bài toán có nghiệm I, ngược lại, 
khi tr > t1 ta chưa thể kết luận là bài toán vô nghiệm. Trường hợp này ta phải tiếp tục 
tích luỹ tr để hi vọng đạt được tổng tr = tk. Nếu sau khi tích luỹ ta thu được tr = tk thì 
bài toán có nghiệm i, ngược lại, khi tr > tk ta kết luận là bài toán vô nghiệm. 
Function Chia(n,k: integer): integer; 
var i: integer; 
 t, t1, tk, tr: longint; 
begin 
 Chia := -1; 
 t := 0; { t = sum(a[1..n]) } 
 for i := 1 to n do t := t+a[i]; 
 if (t mod (k+1) 0) then exit; { vo nghiem } 
 { Xu li truong hop co nghiem } 
 t1 := t div (k+1); { doan tong nho } 
 tk := t - t1; { tk = k * t1} 
 tr := 0; { tong rieng tr = sum(a[1..i]) } 
 for i := 1 to n do 
 begin 
 tr := tr + a[i]; 
 if (tr = t1) or (tr = tk) then 
 begin { lay nghiem i } 
 Chia:= i; exit; 
 end; 
 end; 
end; 
Ta gọi thủ tục Gen để sinh dữ liệu kiểm thử. Cũng giống như bài trước, ta sẽ sinh 
ngẫu nhiên dữ liệu kiểm thử cho hai trường hợp: chắc chắn có nghiệm và có thể vô 
nghiệm. Với trường hợp có thể vô nghiệm ta sinh ngẫu nhiên như bình thường, 
for i := 1 to n do a[i] := random(n); 
Với trường hợp có nghiệm, ta sinh ngẫu nhiên mảng a gồm hai đoạn: 
Đoạn thứ nhất a[1..d] và đoạn thứ hai a[d + 1..n] trong đó d là một điểm chia được 
sinh ngẫu nhiên 
d := random(n div 2)+1; {diem chia} 
Ta lại chọn ngẫu nhiên một trong hai trường hợp: 
Sáng tạo trong Thuật toán và Lập trình Tập I 
22 
Trường hợp thứ nhất: đoạn thứ nhất gấp k lần đoạn thứ hai. 
Trường hợp thứ hai: đoạn thứ hai gấp k lần đoạn thứ nhất. 
(* Pascal *) 
(*------------------------------------------- 
 Chia mang nguyen a thanh 2 doan 
 co tong ti le 1:k 
------------------------------------------ *) 
{$B-} 
uses Crt; 
const MN = 500; 
 Esc = #27;{ dau thoat } 
 bl = #32; { dau cach } 
 nl = #13#10; { xuong dong } 
var a: array [0..MN] of integer; 
 n: integer; 
(*---------------------------------------------- 
 Sinh ngau nhien n so nguyen khong am cho mang a 
----------------------------------------------- *) 
Procedure Gen(m,k: integer); 
 var i,d: integer; t: longint; 
begin 
 n := m; t := 0; 
 if random(2) = 0 then { vo nghiem } 
 begin 
 for i := 1 to n do a[i]:= random(n); 
 exit; 
 end; 
 { co nghiem } 
 d := random(n div 2)+1; { diem chia } 
 for i := 1 to d do 
 begin 
 a[i] := random(n); t := t+a[i]; 
 end; 
 if (random(2) = 0) then 
 { doan a[1..d] gap k lan doan cuoi } 
 a[d] := a[d]+(k-1)*t 
 else { doan cuoi gap k lan doan a[1..d] } 
 t := k*t; 
 for i := d+1 to n-1 do 
 begin 
 a[i] := random(t); t := t-a[i]; 
 end; 
 a[n] := t; 
end; 
Procedure Xem; Hiển thị mảng a, tự viết 
Function Chia(n,k: integer): integer; Tự viết 
Procedure Test; 
 var j,i,k: integer; t: longint; 
 begin 
 randomize; 
 repeat 
Sáng tạo trong Thuật toán và Lập trình Tập I 
23 
 n := 10 + random(10); 
 k := random(5)+1; 
 writeln(nl,' n = ',n,' k = ',k); 
 Gen(n,k); Xem; i := Chia(n,k); 
 if i < 0 then writeln('Khong chia duoc') 
 else 
 begin 
 t := 0; 
 for j := 1 to i do t := t+a[j]; 
 write('Doan 1: a[1..',i,'].'); 
 writeln(' Tong = ',t); 
 t := 0; 
 for j:=i+1 to n do t := t+a[j]; 
 write('Doan 2: a[',i+1,'..',n,'].'); 
 writeln(' Tong = ',t); 
 end; 
 until ReadKey = Esc; 
 end; 
BEGIN 
 Test; 
END. 
// C# 
 using System; 
 using System.Collections.Generic; 
 using System.Text; 
 namespace SangTao1 
 { 
 /*------------------------------------------- 
 * Chia Mang Ti Le 1:k 
 * Chia mang nguyen khomng am a[1..] thanh 
 * hai doan ti le 1:k hoac k:1 
 * ------------------------------------------*/ 
 class ChiaMangTiLe1_k 
 { 
 static void Main(string[] args) 
 { 
 do 
 { 
 Run(10, 3); 
 Console.Write("\n Bam RETURN de tiep tuc, "); 
Console.Write("\n Bam T de thoat: "); 
 } while (Console.ReadLine() != "T"); 
 } 
 static public void Run(int n, int k) 
 { 
 if (n 1000000 || k < 1) return; 
 int[] a = Gen(n, k); 
 Print(a); 
 int d = Chia(a, k); 
 if (d < 0) 
 { 
Sáng tạo trong Thuật toán và Lập trình Tập I 
24 
 Console.WriteLine("\n Vo nghiem"); 
 return; 
 } 
 Console.WriteLine("\n “+ Test(a, d, k)); 
 } 
 // Kiem tra k*Sum(a[1..d]) = Sum(a[d+1..n]) ? 
 // hoac Sum(a[1..d]) = k*Sum(a[d+1..n]) 
 static public bool Test(int[] a, int d, int k) 
 { 
 Console.WriteLine("\n\n Test, k = " + k); 
 Console.WriteLine(" Diem Chia = " + d); 
 int t1 = 0; 
 for (int i = 0; i < d; ++i) t1 += a[i]; 
 int t2 = 0; 
 for (int i = d; i < a.Length; ++i) t2 += a[i]; 
 Console.WriteLine("Sum1 = {0}, Sum2 = {1}", 
 t1, t2); 
 return (t1 == k * t2 || t2 == k * t1); 
 } 
 static public int Chia(int[] a, int k) 
 { 
 int t = 0; 
 foreach (int x in a) t += x; 
 if (t % (k + 1) != 0) return -1; 
 int t1 = t / (k + 1); // tong 1 phan chia 
 int t2 = t - t1; // tong phan con lai 
 int tr = 0; // tong rieng 
 for (int i = 0; i < a.Length; ++i) 
 { 
 tr += a[i]; 
 if (tr == t1 || tr == t2) return i+1; 
 } 
 return -1; 
 } 
 static public int[] Gen(int n, int k) 
 { 
 Random r = new Random(); 
 int[] a = new int[n]; 
 if (r.Next(2) == 0) 
 { // khoang 1/2 so test la vo nghiem 
 for (int i = 0; i < n; ++i) 
 a[i] = r.Next(n); 
 return a; 
 } 
 int d = r.Next(n / 2) + 1; //diem chia 
 int t = 0; 
 int d1 = d - 1; 
 for (int i = 0; i < d1; ++i) 
 { a[i] = r.Next(n); t += a[i]; } 
 if (r.Next(2) == 0) 
 // doan dau a[1..d] 
 // gap k lan doan cuoi a[d+1..n] 
 a[d1] += (k - 1) * t; 

File đính kèm:

  • pdfSáng tạo trong thuật toán và lập trình với ngôn ngữ Pascal và C# - Tập 1 - Chương 1_Giải một bài toán tin.pdf