簡要題意:
平面直角座標系的第一象限有若干綠豬,小鳥要通過若干條函數解析線來消滅它們。每個小鳥可以把所有 上的 的所有綠豬消滅,當然沒有綠豬就是白走。問最少多少次後可以消滅所有的綠豬。 組數據。
前記
實際上,之前寫過一篇 騙分導論,裏面有點口出狂言,既然自己說了能 那還不來填坑?
算法一
對於 的數據,.
實際上,直接爆搜即可。
怎麼搜?對於每個搜索狀態,你可以用一個 來記錄當前 剩下綠豬的座標,並記錄當前步數。
由於 可以方便的刪除元素,我們枚舉當前 的兩個座標並計算出 的值,然後判斷 ,合法則將所有 上的綠豬刪除,進入下一層。
這樣的話,如果你沒有開 ,可以得到 .
開了之後就是 . 臉黑啊
時間複雜度:.
期望得分:. 實際得分:.
暴力最強大!
算法二
對於 的數據,,.
顯然,我們會發現在上面的搜索中,冗餘的狀態太多了。
比方說你先把 一起打下來,再把 一起打下來,和把打它們的順序換一下,本質有什麼區別呢?——沒有,但是搜索中會大量出現類似 重複搜索同一個狀態 的現象,就會想斐波那契數列那樣越滾越大,最終 .
就上面一點而言,假設造個數據,保證每兩頭綠豬都無法一起打下,即每次只能幹掉一個,那這個程序的時間複雜度
所以我們想解決這個問題,但是你認爲如果用 實在是不好,還多一個 ,就和正解陰差陽錯了。所以我們想到,記憶化搜索的本質不就是 ?而枚舉狀態的 不就是
狀態壓縮 ,簡稱狀壓!
我們可以用一個二進制數來表示當前狀態(顯然只有打下和沒打下兩個情況),然後先把所有解析線(至少能打下 頭綠豬的)統計在一起,然後枚舉狀態轉移即可。但這個說法不太清晰,需要建立理論模型。
用 存儲狀態, ~ 即爲所有狀態。把當前解析線能消滅的豬也有一個二進制數存在 裏,每次枚舉狀態的時候,對於每個 都要考慮一個轉移:
f[k|s[i]]=min(f[k|s[i]],f[k]+1);
即如果當前線包含 則直接用線解決;否則 .
但是你發現會有一些綠豬,只能單獨被打,不能和別的豬一起被打。
對於這樣的豬,我們需要再枚舉所有的豬,將它更新一遍。
f[k|(1<<(i-1))]=min(f[k|(1<<(i-1))],f[k]+1);
顯然對於第 個狀態,當前所有豬被打的答案可以直接更新掉,解決了這個問題。
最後 就是答案。
時間複雜度:.(不過 提出了一種 的算法,詳見 AThousandSuns 的洛谷博客)
期望得分:. 實際得分:.
理論上跑滿了約爲 ,挺危險的,擦邊球過去了。
這裏你就會明白爲什麼記憶化搜索過不了,因爲,記憶化用 的時間複雜度是 ,你就多了 倍的常數,成功到了 一個更危險的級別。當然常數好卡過去我也不說什麼了
注意一下:計算解析式的時候可能存在一定精度問題,因此保留 即可。
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
#define db long double
const int N=1e6+1;
inline int read(){char ch=getchar(); int f=1; while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
int x=0; while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return x*f;}
inline void write(int x) {
if(x<0) {putchar('-');write(-x);return;}
if(x<10) {putchar(char(x%10+'0'));return;}
write(x/10);putchar(char(x%10+'0'));
}
int n,m,T,cnt=0;
int f[N],s[501];
db x[101],y[101];
int main() {
T=read(); while(T--) {
n=read(),m=read();
memset(f,0x3f,sizeof(f));
memset(s,0,sizeof(s));
cnt=0; f[0]=0; //初始化
for(int i=1;i<=n;i++) cin>>x[i]>>y[i];
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(x[i]-x[j]) {
double a=(y[i]-y[j]*x[i]/x[j])/x[i]/(x[i]-x[j]);
double b=(y[i]*x[j]*x[j]/x[i]/x[i]-y[j])/(x[j]*x[j]/x[i]-x[j]);
if(a<0) { //計算合法的解析式
cnt++;
for(int k=1;k<=n;k++)
if(fabs(a*x[k]*x[k]+b*x[k]-y[k])<=1e-6) s[cnt]|=(1<<(k-1));
} //記錄解析式
}
for(int k=0;k<=(1<<n)-1;k++) {
for(int i=1;i<=cnt;i++) f[k|s[i]]=min(f[k|s[i]],f[k]+1);
for(int i=1;i<=n;i++) f[k|(1<<(i-1))]=min(f[k|(1<<(i-1))],f[k]+1);
} printf("%d\n",f[(1<<n)-1]);
}
return 0;
}