【狀壓DP】【cofun1760】angrybirds

【cofun1760】angrybirds

Description
這裏寫圖片描述
這裏寫圖片描述
這裏寫圖片描述
這裏寫圖片描述
這裏寫圖片描述
這裏寫圖片描述
這裏寫圖片描述
這裏寫圖片描述


  • 分析:
    • 1 <= n <= 18
      考慮狀壓DP
      ①顯然函數種類<=n^2,用數組g[i][j]表示打i,j這兩隻小豬的函數能打到的所有小豬的情況(0:該小豬在函數上/ 1:該小豬不在函數上),可解方程求出函數,然後判斷小豬是否符合函數。
      ②狀壓DP,轉移方程:
 f[i | g[j][k]] = min(f[i | g[j][k]], f[i] + 1);

注意到也可以讓函數只通過一隻小豬,特判一下:

f[i | (1 << j)] = min(f[i | (1 << j)], f[i] + 1);

f[i] :狀態爲i時射出最少的小鳥數


  • 代碼:
#include <bits/stdc++.h>
 using namespace std;

 int T, n, m, i, j, k, f[1 << 20], g[20][20];
 double x[20], y[20], a, b;

 bool equal(double x, double y)
 {
    return (fabs(x - y) <= 1e-6);
 }//判斷x,y是否相等,精度差控制在1e-6中 

 int main()
 {

    for(scanf("%d\n", &T); T; T --)
    {
        scanf("%d%d\n", &n, &m);
        for(i = 0; i < n; i ++)
            scanf("%lf%lf\n", &x[i], &y[i]);
        //讀入 
        memset(g, 0, sizeof(g));
        for(i = 0; i < n; i ++)
            for(j = i + 1; j < n; j ++)
                if (! equal(x[i], x[j]))
                {
                    a = (y[i] / x[i] - y[j] / x[j]) / (x[i] - x[j]);
                    if (a >= 0)
                        continue;
                    b = y[i] / x[i] - a * x[i];
                    g[i][j] = 1 << i | 1 << j;
                    for(k = j + 1; k < n; k ++)
                        if (equal(y[k], a * x[k] * x[k] + b* x[k]))
                            g[i][j] |= 1 << k;
                 }
        //預處理不同函數的情況①        
        for(i = 1; i < 1 << n; i ++)
            f[i] = 0x3f3f3f;
        f[0] = 0;

        for(i = 0; i < 1 << n; i ++)
            for(j = 0; j < n; j ++)
                if (! (i & 1 << j))
                {
                    for(k = j; k < n; k ++)
                        if (j == k)
                            f[i | (1 << j)] = min(f[i | (1 << j)], f[i] + 1);
                        else
                        if (! equal(x[k], x[j]))
                            f[i | g[j][k]] = min(f[i | g[j][k]], f[i] + 1);
                 }
        //狀壓DP②      
        printf("%d\n", f[(1 << n) - 1]);
        //輸出 
     }

    return 0;

 }
  • 時間複雜度O(2n * n2 )

  • 由於數據不大而且題目給出剪枝條件,這道題也可以爆搜通過。

  • 具體實現就是:對於每個點,分類討論兩種情況:自己新建一條拋物線,或者和之前的某一個點組合形成一條完整的拋物線。因爲拋物線的式子只有2個變量,所以我們只需要2個點就可以確定這一條拋物線。搜索的時候,保存之前的每個點的狀態,一個是已經由2個或以上的點形成的拋物線的a、b值,這樣可以一開始就計算是不是已經被之前形成的拋物線經過了,如果經過了則可以直接搜下一個點。另外,還需要保存之前有哪些點尚未完成“配對”,即之前確定爲是新建了一條拋物線之後,因爲只有一個點不能確定拋物線的解析式,所以尚未完成“配對”。我們考慮當前點和之前沒有“配對”的點一一組合形成新的拋物線,再往下搜索。值得注意的是,根據題意拋物線的解析式中的a一定<0,所以並不是算出來的解析式一定可行。通過兩個點算解析式的具體算法不贅述了,這屬於初中的二次函數數學題了。
      這個算法當然可以套上最優性剪枝,可以避免大量不必要的搜索。題目中給的m也可以用於剪枝(初值的設定)。
      因爲最優性剪枝事實上可以減去大量無用狀態,所以這個搜索是可以通過n<=18的所有數據的。
    【上面這段分析是本po從老師給的課件中copy過來的,如果侵權了請指出,本po會改正並道歉的,謝謝。

【本po去年沒學狀壓,爆搜才35分。。是真的蒟蒻OTL


(~ ̄▽ ̄)~早上好,同學們~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章