【cofun1760】angrybirds
Description
- 分析:
- 1 <= n <= 18
考慮狀壓DP
①顯然函數種類<=n^2,用數組g[i][j]表示打i,j這兩隻小豬的函數能打到的所有小豬的情況(0:該小豬在函數上/ 1:該小豬不在函數上),可解方程求出函數,然後判斷小豬是否符合函數。
②狀壓DP,轉移方程:
- 1 <= n <= 18
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
(~ ̄▽ ̄)~早上好,同學們~