NOIP2004賽題
NOIP2004提高組複賽最後一題:蟲食算。蟲食算
(alpha.pas/dpr/c/cpp)
問題描述
所謂蟲食算,就是原先的算式中有一部分被蟲子啃掉了,需要我們根據剩下的數字來判定被啃掉的字母。來看一個簡單的例子:43#98650#45
+ 8468#6633
44445506978
其中#號代表被蟲子啃掉的數字。根據算式,我們很容易判斷:第一行的兩個數字分別是5和3,第二行的數字是5。
現在,我們對問題做兩個限制:
首先,我們只考慮加法的蟲食算。這裡的加法是N進制加法,算式中三個數都有N位,允許有前導的0。
其次,蟲子把所有的數都啃光了,我們只知道哪些數字是相同的,我們將相同的數字用相同的字母表示,不同的數字用不同的字母表示。如果這個算式是N進制的,我們就取英文字母表午的前N個大寫字母來表示這個算式中的0到N-1這N個不同的數字:但是這N個字母並不一定順序地代表0到N-1)。輸入數據保證N個字母分別至少出現一次。
BADC
+ CBDA
DCCC
上面的算式是一個4進制的算式。很顯然,我們只要讓ABCD分別代表0123,便可以讓這個式子成立了。你的任務是,對於給定的N進制加法算式,求出N個不同的字母分別代表的數字,使得該加法算式成立。輸入數據保證有且僅有一組解,
輸入檔案
輸入檔案包含4行。第一行有一個正整數N(N<=26),後面的3行每行有一個由大寫字母組成的字元串,分別代表兩個加數以及和。這3個字元串左右兩端都沒有空格,從高位到低位,並且恰好有N位。輸出檔案
輸出檔案alpha.out包含一行。在這一行中,應當包含唯一的那組解。解是這樣表示的:輸出N個數字,分別表示A,B,C……所代表的數字,相鄰的兩個數字用一個空格隔開,不能有多餘的空格。樣例輸入
5ABCED
BDACE
EBBAA
樣例輸出
1 0 3 4 2數據規模
對於30%的數據,保證有N<=10;對於50%的數據,保證有N<=15;
對於全部的數據,保證有N<=26。
解題報告
經典的搜尋題。最單純的搜尋的時間複雜度為O(n!),是會非常嚴重的逾時的。計算機是很“笨”的,它不會思考,在盲目搜尋的過程中,很容易出現這種情況:計算機在某一位搜尋出了一個算式1 + 1 = 3,並且繼續搜尋。
明顯,人眼很容易就看出這是不合法的,但計算機不會。於是,我們想到了第一個剪枝:每次搜尋的時候,從最後向前判斷是否有不合法的式子。
這一個剪枝非常簡單,但是效果卻非常的好。因為它剪去了很多不必要的搜尋。為了配合這一種剪枝更好的實行,搜尋順序的改變也成為大大提高程式效率的關鍵:從右往左,按照字母出現順序搜尋,有很大程度上提高了先剪掉廢枝的情況,使程式的效率得到大大的提高。
有了以上兩個剪枝,程式就已經可以通過大部分測試點了。但是有沒有更多的剪枝呢?答案是肯定的。
根據前面的剪枝,我們可以找到類似的幾個剪枝:
對於a + b = c的形式,假如:
A***?***
+ B*?**?**
C***???*
其中*代表已知,?代表未知。那么,A + B與C的情況並不能直接確定。但是,假如(A + B) % N與(A + B + 1) % N都不等於C的話,那么這個等式一定是不合法的。因為它只有進位和不進位的兩種情況。
同樣,我們在一個數組里記錄了Used表示一個數字有沒有用過,那么,對於某一位A + B = C的等式,如果已經得到了兩個數,另一個數還待搜尋的時候,我們還可以根據這個加入一個剪枝:
例如A + ? = C的形式,
考慮不進位的情況,則?處為P1 = (C - A + N) % N
假如考慮進位的情況,則?處為P2 = (C - A - 1 + N) % N
假如P1、P2均被使用過,那么這個搜尋一定是無效的,可以剪去。
有了以上的剪枝,就可以很輕鬆地通過所有的測試數據了。當然,還有很多值得思考的剪枝以及其他的思路,例如枚舉進位、解方程(但是可能需要枚舉)等,在這裡就不詳細討論了。
代碼清單
C++語言實現
#include#include
using namespace std;
ifstream fin;
ofstream fout("alpha.out");
bool finish, hash[256], used[27];
int n, stk[27];
string a, b, c;
string word;
void init() {
fin >> n >> a >> b >> c;
finish = false;
}
void outsol() {
int i, ans[27];
for (i = 0; i < n; i ++)
ans[word - 65] = stk;
fout << ans[0];
for (i = 1; i < n; i ++)
fout << " " << ans;
fout << endl;
finish = true;
}
void addup(char ch) {
if (!hash[ch]) {
hash[ch] = true;
word = word + ch;
}
}
string change(string str, char x, char y) {
for (int i = 0; i < n; i ++)
if (str == x)
str = y;
return str;
}
void pre_doing() {
word = "";
memset(hash, 0, sizeof(hash));
for (int i = n - 1; i >= 0; i --) {
addup(a); addup(b); addup(c);
}
memset(used, 0, sizeof(used));
}
bool bad() {
int p, g = 0;
for (int i = n - 1; i >= 0; i --) {
if (a >= n || b >= n || c >= n) return false;
p = a + b + g;
if (p % n != c) return true;
g = p / n;
p %= n;
}
return false;
}
bool modcheck() {
int i, p, p1, p2, g = 0;
//a + b = c, all know
for (i = n - 1; i >= 0; i --) {
if (a >= n || b >= n || c >= n) continue;
p = (a + b) % n;
if (!(p == c || (p + 1) % n == c)) return true;
}
//a + ? = c
for (i = n - 1; i >= 0; i --) {
if (!(a < n && c < n && b >= n)) continue;
p1 = (c - a + n) % n;
p2 = (p1 - 1) % n;
if (used[p1] && used[p2]) return true;
}
//? + b = c
for (i = n - 1; i >= 0; i --) {
if (!(a >= n && c < n && b < n)) continue;
p1 = (c - b + n) % n;
p2 = (p1 - 1) % n;
if (used[p1] && used[p2]) return true;
}
//a + b = ?
for (i = n - 1; i >= 0; i --) {
if (!(a < n && b < n && c >= n)) continue;
p1 = (a + b) % n;
p2 = (p1 + 1) % n;
if (used[p1] && used[p2]) return true;
}
return false;
}
void dfs(int l) {
int i;
string A, B, C;
if (finish) return;
if (bad()) return;
if (modcheck()) return;
if (l == n) {
outsol();
return;
}
for (i = n - 1; i >= 0; i --)
if (!used) {
used = true; A = a; B = b; C = c;
a = change(A, word[l], i);
b = change(B, word[l], i);
c = change(C, word[l], i);
stk[l] = i;
dfs(l + 1);
used = false; a = A; b = B; c = C;
}
}
int main() {
init();
pre_doing();
dfs(0);
return 0;
}
pascal實現
vara:Array[1..3,1..26]of char;
b:array['A'..'Z']of longint;
c:array[0..25]of boolean;
i,j,n:longint;
function check(x,m:longint):boolean;
var
yy:boolean;
i,j:longint;
begin
if ((b[a[1,x]]+b[a[2,x]]+m)mod n<>b[a[3,x]]) then exit(false);
for i:=1 to x-1 do
if (b[a[1,i]]<>-1)and(b[a[2,i]]<>-1)and(b[a[3,i]]<>-1)then
begin
yy:=true;
for j:=0 to 1 do
if (b[a[1,i]]+b[a[2,i]]+j)mod n=b[a[3,i]] then yy:=false;
if yy then exit(false);
end;
check:=true;
end;
procedure dfs(x,m:longint);
var i,j,d,p,mm:longint;
aa,bb,cc:boolean;
begin
if x=0 then begin
if m=0 then
begin
for i:=1 to n do write(b[chr(i+64)],' ');
close(input);close(output);
halt;
end;
exit;
end;
aa:=false;
bb:=false;
cc:=false;
if b[a[1,x]]<>-1 then aa:=true;
if b[a[2,x]]<>-1 then bb:=true;
if b[a[3,x]]<>-1 then cc:=true;
if (aa and bb and cc) then
begin
if check(x,m) then dfs(x-1,(m+b[a[1,x]]+b[a[2,x]])div n);
end
else if aa and bb then
begin
d:=(b[a[1,x]]+b[a[2,x]]+m)mod n;
if c[d] then
begin
b[a[3,x]]:=d;
c[d]:=false;
mm:=(b[a[1,x]]+b[a[2,x]]+m)div n;
if check(x,m) then dfs(x-1,mm);
c[d]:=true;
b[a[3,x]]:=-1;
end;
end
else if aa and cc then
begin
d:=(b[a[3,x]]-m-b[a[1,x]]+n)mod n;
if c[d] then
begin
b[a[2,x]]:=d;
c[d]:=false;
mm:=(d+b[a[1,x]]+m)div n;
if check(x,m) then dfs(x-1,mm);
m:=mm;
c[d]:=true;
b[a[2,x]]:=-1;
end;
end
else if bb and cc then
begin
d:=(b[a[3,x]]-m-b[a[2,x]]+n)mod n;
if c[d] then
begin
b[a[1,x]]:=d;
c[d]:=false;
mm:=(d+b[a[2,x]]+m)div n;
if check(x,m) then dfs(x-1,mm);
m:=mm;
c[d]:=true;
b[a[1,x]]:=-1;
end;
end
else if (not aa)and(not bb)and(not cc) then
begin
if a[1,x]<>a[2,x] then
begin
for i:=n-1 downto 0 do
for j:=n-1 downto 0 do
if (c[i] and c[j])and(i<>j) then
begin
c[i]:=false;
c[j]:=false;
b[a[1,x]]:=j;
b[a[2,x]]:=i;
dfs(x,m);
c[i]:=true;
c[j]:=true;
b[a[1,x]]:=-1;
b[a[2,x]]:=-1;
end;
end
else
for i:=n-1 downto 0 do
if c[i] then
begin
b[a[1,x]]:=i;
c[i]:=false;
dfs(x,m);
b[a[p,x]]:=-1;
c[i]:=true;
end;
end
else begin
for i:=n-1 downto 0 do
if c[i] then
begin
if not aa then p:=1
else if not bb then p:=2
else if not cc then p:=3;
b[a[p,x]]:=i;
c[i]:=false;
dfs(x,m);
b[a[p,x]]:=-1;
c[i]:=true;
end;
end;
end;
begin
readln(n);
for i:=1 to 3 do
begin
for j:=1 to n do
read(a[i,j]);
readln;
end;
fillchar(b,sizeof(b),255);
fillchar(c,sizeof(c),true);
dfs(n,0);
end.
總結
搜尋題的框架往往不難找到,關鍵就是在搜尋的最佳化上,本文的主要篇幅也就是討論了幾種有效的最佳化。搜尋問題的最佳化更多的需要選手的經驗和思考、分析問題的能力,所以搜尋剪枝也是競賽中經久不衰的經典問題。
數學遊戲
數學遊戲即包含了數學中的遊戲和使用數學玩的遊戲。大部分數學遊戲的規則都非常簡單,但解決它們時,有時卻需用到很高深的幾何學、圖論、拓撲學、組合數學、邏輯學或博弈論等的知識。對某些數學遊戲的研究,更有助推動一些數學話題的發展。不少數學家都是數學遊戲的愛好者。 |