NOIP提高組—— 問題求解 與 完善程序
問題求解1:
甲乙丙丁四人在考慮周末要不要外出郊遊。
已知①如果周末下雨,並且乙不去,則甲一定不去;②如果乙去,則丁一定去;③如果丙去,則丁一定不去;④如果丁不去,而且甲不去,則丙一定不去。如果周末丙去了,則甲___去了___(去了/沒去)(1 分),乙___沒去___(去
了/沒去)(1 分),丁____沒去__(去了/沒去)(1 分),周末___沒下雨___(下雨/沒下雨)(2 分)。
分析:大水題,送分。只要別寫錯字就好了。
證明:
丙去了,聯系③,丁不會去,一分 get
丁沒去,聯系②,乙不會去,一分 get
丁沒去,丙去了,聯系④,甲會去,一分 get
乙不去,甲去了,聯系①,不會下雨,一分 get
問題求解2:
方程 a*b = (a or b) * (a and b),在 a,b 都取 [0, 31] 中的整數時,共有 454 組解。(* 表示乘法;or 表示按位或運算;and 表示按位與運算)
分析:考場上蒙了蒙,感覺等式成立的條件應該是 a 是 b 二進制下的子集或者 b 是 a 的子集。
然鵝並沒有證明出來,於是就組合數搞了一下 A 掉了
(我同桌其實也 A 掉了,而且他還證出來了只不過他算錯了2333)。
於是先證明一下:
不妨設 t = a- (a & b) 。(一開始我是設 t = a&b 的,所以死活證不出來), 於是 (x | y) = (y+t)
然後我們將 t 帶入式子,得到: $a * b = ( a - t ) * ( b + t )$ 。
然後展開
=> $a * b = a*b + a*t - b*t - t*t$
=> $a*t - b*t - t*t = 0$
=> $t * (a - b - t) = 0$
=> 1. t=0 ; 2. a=b+t
考慮 t=0 的情況,t 等於零 意味著 a = a&b ,那麽也就說明二進制下的 a 被包含於 b 中。
再考慮 a=b+t 的情況,這意味著 b=a&b,那麽也就說明二進制下的 b 被包含於 a 中。
於是我們得出結論: 若原式成立,則在二進制下,a 是 b 的子集,或者 b 是 a 的子集。
然後我們考慮到如果 a=b ,那麽原式必然成立,所以我們先將答案加上 32 (0~31每個數都與自己匹配),然後我們去討論 b 是 a 的 真 子集情況,然後把討論出來的答案 * 2 累加上去就好了。
那麽我們先考慮 a 的數值。如果說這時候我們枚舉 32 次,答案也是能出來的,但是這樣做不僅低效率而且容易出錯,那麽我們考慮在用二進制的方法枚舉 a 。
其實既然說了 b 是 a 的二進制真子集了,那麽其實 我們只需要枚舉在 log32 位空格(也就是 5 個空格)裏面,放 k(k=0~5) 個 1 的方案就好了,因為這些數對答案的貢獻是相同的,都是 k 個 1 裏面計算真子集數。
那麽方案數也就是組合數 C(5,k) 了。然後我們考慮在這 k 個位置裏面安排 b 。 那麽 b 的數值方案數也還是組合數,就是 $sigma_{s=1}^{k} C(k,s)$ 了。
然後我們將 $sigma_{s=1}^{k} C(k,s)$ 乘上 C(5,k),再乘以二,累加進答案就好了。
最近我聽說這道題是吉老師出的啊?而且原數據範圍是 [ 0~1023 ] 啊?不過後來誰驗題後改成 [ 0~32 ] 了? 反正不是杜雨皓【逃】
於是我們得出結論:吉老師是鐵了心要給 zjoi 選手蓋上棺材板了 (但願復賽...咳咳)
emmm...其實1024的範圍用上面的方法是可以做初步的運算的(唔,別打臉),只不過應該是要用到更高深的組合數學理論了吧?(反正我不會,組合數沒好好學哈~)
完善程序1:
對於一個1到 ?? 的排列 ?? (即1到??中每一個數在??中出現了恰好一次),令 ???? 為第 ?? 個位置之後第一個比 ???? 值更大的位置,如果不存在這樣的位置,則 ???? = ?? +1 。舉例來說,如果 ?? = 5且 ?? 為1 5 4 2 3,則 ?? 為2 6 6 5 6。
下列程序讀入了排列 ??,使用雙向鏈表求解了答案。試補全程序。(第二空2 分,其余3 分)
數據範圍 1 ≤ ?? ≤ 105。
#include <iostream> using namespace std; const int N = 100010; int n; int L[N], R[N], a[N]; int main() { cin >> n; for (int i = 1; i <= n; ++i) { int x; cin >> x; (1)a[x]=i ; } for (int i = 1; i <= n; ++i) { R[i] = (2)i+1 ; L[i] = i - 1; } for (int i = 1; i <= n; ++i) { L[ (3)R[a[i]] ] = L[a[i]]; R[L[a[i]]] = R[ (4)a[i] ]; } for (int i = 1; i <= n; ++i) { cout << (5)R[i] << " "; } cout << endl; return 0; }
不是很想說,因為我掛了。
這個其實就是雙向鏈表,我們看第一個空,a[i] 就是指數字 i 所在的位置。
然後後面的三個空其實都是上下文對照著填就對了。 最後一個空的話,可以看出來是輸出 R[i] :位置 i 右邊比它大的最近的數。
這個我沒什麽技巧了,畢竟都做錯了。就是猜著填什麽,模擬一遍對了就過吧。
完善程序2:
一只小豬要買N 件物品(N 不超過1000)。它要買的所有物品在兩家商店裏都有賣。第i 件物品在第一家商店的價格是a[i],在第二家商店的價格是b[i],兩個價格都不小於0 且不超過10000。如
果在第一家商店買的物品的總額不少於50000,那麽在第一家店買的物品都可以打95 折(價格變為原來的0.95 倍)。
求小豬買齊所有物品所需最少的總額。
輸入:第一行一個數N。接下來N 行,每行兩個數。第i 行的兩個數分別代
表a[i],b[i]。
輸出:輸出一行一個數,表示最少需要的總額,保留兩位小數。
試補全程序。(第一空2 分,其余3 分)
於是不做分析,代碼裏面解釋。
#include <cstdio> #include <algorithm> using namespace std; const int Inf = 1000000000; const int threshold = 50000; const int maxn = 1000; int n, a[maxn], b[maxn]; //表示價格 bool put_a[maxn]; //put_a[i]表示第 i 個物品是不是在 a 商場買的 int total_a, total_b; //表示在 a 商場和 b 商場耗費的金錢 double ans; int f[threshold]; //這個是重點: f[i] 表示從 當前前綴狀態下, 買總價格為 i 的物品,所需要去 b 商場的最小花費 int main() { scanf("%d", &n); total_a = total_b = 0; for (int i = 0; i < n; ++i) { scanf("%d%d", a + i, b + i); if (a[i] <= b[i]) total_a += a[i]; else total_b += b[i]; } ans = total_a + total_b; //不考慮優惠,直接算一遍答案 total_a = total_b = 0; for (int i = 0; i < n; ++i) { if ( (1)a[i]*0.95<=b[i] ) {//考慮優惠且必須優惠(因為不優惠的狀況考慮過了),只要 a[i] 在優惠狀態下 <= b[i] 就選 a put_a[i] = true; total_a += a[i]; } else { put_a[i] = false; total_b += b[i]; } } if ( (2)total_a>=50000 ) { //如果說total_a 已經達到優惠所需最小花費,那麽直接輸出答案(這時把任何 b 商場裏的東西換到 a 商場買不會更優) printf("%.2f", total_a * 0.95 + total_b); return 0; } f[0] = 0; for (int i = 1; i < threshold; ++i) f[i] = Inf; int total_b_prefix = 0; for (int i = 0; i < n; ++i) if (!put_a[i]) { //如果是去 b 商場買的就考慮背包轉移 total_b_prefix += b[i]; //當前去 b 商場買的物品所花費總代價 for (int j = threshold - 1; j >= 0; --j) { if ( (3)total_a + j + a[i] >= threshold && f[j] != Inf) //如果說可以轉移 ans = min(ans, (total_a + j + a[i]) * 0.95 + (4)total_b + f[j] - total_b_prefix ); //判斷是否更優
//當前去 a 商場的總花費加上背包體積以及當前物品,乘以折扣,在加上去 b 商場購買的總價值,是否小於 ans f[j] = min(f[j] + b[i], j >= a[i] ? (5)f[j-a[i]] : Inf); //背包轉移
//考慮當前的 j 大小的背包是加上b[i] 更優還是直接靠 f[j-a[i]] 轉移更優(就是考慮當前的背包是加上代價還是狀態轉移) } } printf("%.2f", ans); return 0; }
NOIP提高組—— 問題求解 與 完善程序