1. 程式人生 > >NOIP提高組—— 問題求解 與 完善程序

NOIP提高組—— 問題求解 與 完善程序

方法 sof efi out 接下來 就是 聽說 提高 條件

問題求解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提高組—— 問題求解 與 完善程序