【題解】NOIP2016提高組 復賽
傳送門:
- 玩具謎題 \(\text{[P1563]}\)
- 天天愛跑步 \(\text{[P1600]}\)
- 換教室 \(\text{[P1850]}\)
- 組合數問題 \(\text{[P2822]}\)
- 蚯蚓 \(\text{P[2827]}\)
- 憤怒的小鳥 \(\text{P[2831]}\)
【Day1】
【T1】
【題目描述】
有 \(n\) \((n \leqslant 10^5)\) 個小人圍成一圈(逆時針給出),已知它們的姓名 \(name[i]\) 和面朝的方向 \(a[i]\)(內或外)。
現從位置 \(1\) 開始,給出 \(m\) 條指令,每條指令將給出移動方向 \(x\)(向左或向右)和移動距離 \(y\),輸出每次執行指令后所在位置的小人姓名。
【分析】
隨便膜你一下就好了。
用一個變量 \(p\) 維護當前所在位置,為方便環上取膜處理,把 \([1,n]\) 映射為 \([0,n-1]\) 。
\(a[p]=0,\) \(x=0\):朝內,向左。小人編號 \(-y\) 。
\(a[p]=0,\) \(x=1\):朝內,向右。小人編號 \(+y\) 。
\(a[p]=1,\) \(x=0\):朝外,向左。小人編號 \(+y\) 。
\(a[p]=1,\) \(x=1\):朝外,向左。小人編號 \(-y\) 。
可以發現 \(a[p]\) 與 \(x\) 的異或值為 \(1\) 時編號為增加,為 \(0\) 時減少。
沒什么細節,也沒什么坑點,這才是真正的送分題。
【Code】
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#define Re register int
using namespace std;
const int N=1e5+3;
int n,m,x,y,a[N];char name[N][13];
inline void in(Re &x){
int f=0;x=0;char c=getchar();
while(c<'0'||c>'9')f|=c=='-',c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=f?-x:x;
}
int main(){
// freopen("toy.in","r",stdin);
// freopen("toy.out","w",stdout);
in(n),in(m);
for(Re i=0;i<n;++i)in(a[i]),scanf("%s",name[i]);
Re p=0;
while(m--){
in(x),in(y);
x^=a[p];
if(x)p=(p+y)%n;
else p=(p-y+n)%n;
}
printf("%s",name[p]);
fclose(stdin);
fclose(stdout);
return 0;
}
【T2】
【題目描述】
給出一棵 \(n\) \((n \leqslant 3*10^5)\) 個節點的樹,每個節點上的觀察員會在 \(w_{i}\) \((0 \leqslant w_{i} \leqslant n)\) 時進行觀測,如果此時恰好有玩家在此節點上,那么觀察員可以觀測到該玩家。
現給出 \(m\) \((m \leqslant 3*10^5)\) 個玩家的運動路線起點 \(st_{j}\) \((1 \leqslant st_{j} \leqslant n)\)、終點 \(ed_{j}\) \((1 \leqslant ed_{j} \leqslant n)\),在第 \(0\) 秒時,所有人都會同時出發直至到達終點。
求每個觀察員可以觀測到多少個玩家。
【分析】
一道碼農題。
對於每個節點 \(x\),考慮在它的子樹中能對它產生貢獻的點有哪些:
\((1).\) 玩家起點在 \(x\) 的子樹中。需滿足 \(deep[st_{j}]=deep[x]+w[x]\) 。
\((2).\) 玩家終點在 \(x\) 的子樹中。需滿足 \(dis(st_{j},ed_{j})=w[x]+(deep[ed_{j}]-deep[x])\),易知 \(dis(st_{j},ed_{j})=\) \(deep[st_{j}]+deep[ed_{j}]-2*deep[lca(st_{j},ed_{j})]\),化簡得:\(deep[st_{j}]-2*deep[lca(st_{j},ed_{j})]=w[x]-deep[x]\) 。
於是問題被轉換成了:在 \(x\) 的子樹中找到滿足上述等式的 \(j\) 的個數。
為方便描述,將上述兩個等式視為 \(s_{1}=deep[x]+w[x]\) 和 \(s_{2}=w[x]-deep[x]\)。
一種方法是開桶然后直接差分,但智商不夠,就只有數據結構來湊了。
對每個節點開一棵動態開點權值線段樹,對於每條路徑 \(dis(st_{j},ed_{j})\),把路徑 \(dis(st_{j},lca(st_{j},ed_{j}))\) 上的所有線段樹的 \(s_{1}\) 都加 \(1\),\(dis(ed_{j},lca(st_{j},ed_{j}))\) 上的所有線段樹的 \(s_{2}\) 都減 \(1\),這個操作可以通過樹上差分+線段樹合並實現。
總結:\(lca\) \(+\) 樹上差分 \(+\) 權值線段樹 \(+\) 動態開點 \(+\) 線段樹合並。豆粽強者,恐怖如斯。。
其實用樹剖應該會更簡單的啦。
【Code】
#include<algorithm>
#include<cstdio>
#define Re register int
using namespace std;
const int N=3e5+3,logN=19;
int x,y,n,m,T,o,lca,w[N],pt[N],ans[N],head[N],deep[N];
struct QWQ{int to,next;}a[N<<1];
inline void add(Re x,Re y){a[++o].to=y,a[o].next=head[x],head[x]=o;}
inline void in(Re &x){
x=0;char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}
struct Segmemt_Tree{
#define pl tr[p].lp
#define pr tr[p].rp
#define mid (L+R>>1)
struct QAQ{int g,lp,rp;}tr[N*36];int cnt;//大約要開4nlog(2n)個節點
inline void change(Re &p,Re L,Re R,Re x,Re v){
if(!p)p=++cnt;
if(L==R){tr[p].g+=v;return;}
if(x<=mid)change(pl,L,mid,x,v);
else change(pr,mid+1,R,x,v);
}
inline int merge(Re p,Re q){
if(!p)return q;if(!q)return p;
tr[p].g+=tr[q].g;
pl=merge(pl,tr[q].lp);
pr=merge(pr,tr[q].rp);
return p;
}
inline int ask(Re p,Re L,Re R,Re x){
if(!p)return 0;
if(L==R)return tr[p].g;
if(x<=mid)return ask(pl,L,mid,x);
else return ask(pr,mid+1,R,x);
}
}T1;
struct LCA{//倍增lca
int ant[N][23];
inline void dfs(Re x,Re fa){
deep[x]=deep[ant[x][0]=fa]+1;
for(Re i=1;(1<<i)<=deep[x];++i)ant[x][i]=ant[ant[x][i-1]][i-1];
for(Re i=head[x];i;i=a[i].next)if(a[i].to!=fa)dfs(a[i].to,x);
}
inline int lca(Re x,Re y){
if(deep[x]<deep[y])swap(x,y);
for(Re i=logN;i>=0;--i)if(deep[ant[x][i]]>=deep[y])x=ant[x][i];
if(x==y)return x;
for(Re i=logN;i>=0;--i)if(ant[x][i]!=ant[y][i])x=ant[x][i],y=ant[y][i];
return ant[x][0];
}
}T2;
inline void dfs(Re x,Re fa){
for(Re i=head[x],to;i;i=a[i].next)
if((to=a[i].to)!=fa)
dfs(to,x),pt[x]=T1.merge(pt[x],pt[to]);//合並子樹信息
ans[x]+=T1.ask(pt[x],1,n<<1,n+deep[x]-w[x]);
if(w[x]&&deep[x]+w[x]<=n)ans[x]+=T1.ask(pt[x],1,n<<1,n+deep[x]+w[x]);
//當w[x]為0時會算兩遍所以要特判,還有不能越界
}
int main(){
// freopen("running.in","r",stdin);
// freopen("running.out","w",stdout);
in(n),in(T),m=n-1;
while(m--)in(x),in(y),add(x,y),add(y,x);
for(Re i=1;i<=n;++i)in(w[i]);
T2.dfs(1,0);
while(T--){
in(x),in(y),lca=T2.lca(x,y);
T1.change(pt[x],1,n<<1,n+deep[x],1);//可能會有負數,把所有下標都加n
T1.change(pt[y],1,n<<1,n+(deep[lca]<<1)-deep[x],1);
T1.change(pt[lca],1,n<<1,n+deep[x],-1);
T1.change(pt[T2.ant[lca][0]],1,n<<1,n+(deep[lca]<<1)-deep[x],-1);
}
dfs(1,0);
for(Re i=1;i<=n;++i)printf("%d ",ans[i]);
fclose(stdin);
fclose(stdout);
return 0;
}
【T3】
【題目描述】
有 \(ng\) \((ng \leqslant 2000)\) 個時間點,第 \(i\) 個時間點有兩個教室可使用(默認使用 \(A_{0}[i]\)),可以提交最多 \(mg\) \((mg \leqslant 2000)\) 個申請,每次申請有 \(K_{i}\) 的概率通過,如果通過,那么第 \(i\) 個時間點的使用教室將會由 \(A_{0}[i]\) 換成 \(A_{1}[i]\) 。
一共 \(n\) \((n \leqslant 300)\) 個教室 \(m\) \((m \leqslant 90000)\) 條邊構成一張無向圖,每上完第 \(i\) 個時間點的課后,會選擇一條最短路徑走向第 \(i+1\) 個時間點的教室。
求:在哪幾個時間點上申請交換教室可以使得所走的總路徑期望值最小,只需要輸出這個最小值即可。
【分析】
本以為 \(T2\) 已經夠毒瘤了,沒想到 \(T3\) 更 \(\text{bt}\) 。
第一次做期望題,一臉懵逼的我就只能在子任務中尋找騙分點。
特殊性質1:一顆樹?然並卵。n<=300的完全圖你能卡我?你要是能卡我,我就,我就...
特殊性質2:凡是選了出來的一定會交換
觀察性質 \(2\),發現一切與概率有關的東西都可以忽略,直接上 \(dp\),再加上 \(n=1\) 的特判(輸出 \(0.00\)),\(28\) 分就到手了,做法如下:
由於圖是固定不變的,可以用 \(Floyed\) 預處理出每對點的最短路,也可以用 \(dijkstra\)(理論時間復雜度優一些)。
\(dp[k][i][0]\) 表示已經在前 \(i\) 個時間點中選出了 \(k\) 個,且第 \(i\) 個不選的總路徑最小值,
\(dp[k][i][1]\) 表示已經在前 \(i\) 個時間點中選出了 \(k\) 個,且第 \(i\) 個被選的總路徑最小值。
那么有:
現在考慮加入概率,狀態表示不變,遞推形式也基本相同,但轉移的時候就需要變化一下。
假設第 \(i\) 個時間點沒有選,那么一定是在 \(A_{0}[i]\),即有 \(1\) 的概率在 \(A_{0}[i]\),有 \(0\) 的概率在 \(A_{1}[i]\)。如果選了,那么將有 \(K_{i}\) 的概率在 \(A_{1}[i]\),\(1\!-\!K[i]\) 的概率在 \(A_{0}[i]\),
因此在計算 \(dis(A_{x_{i-1}}[i\!-\!1],A_{x_{i}}[i])\) 的時候就應乘上 \(x_{i-1}\) 的概率 \(P_{x_{i-1}}(i\!-\!1)\) 再乘以 \(x_{i}\) 的概率 \(P_{x_{i}}(i)\),即:
\(dp[k][i][0/1]=min\{dp[k][i][0/1]+dis(A_{0/1}[i\!-\!1],A_{0/1}[i])*P_{0/1}(i\!-\!1)*P_{0/1}(i)\}\)
對於 \(dp[k][i][0]\),\(P_{0}(i)=1,\) \(P_{1}(i)=0\),
對於 \(dp[k][i][1]\),\(P_{0}(i)=1\!-\!K[i],\) \(P_{1}(i)=K[i]\) 。
方程見代碼。
時間復雜度為:\(O(n^2)\) 。
【Code】
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<queue>
#define LD double
#define Re register int
using namespace std;
const int N=303,M=90003,G=2003,inf=1e9,eps=1e-8;
int n,m,x,y,z,o,ng,mg,flagT2=1,A[G][2],head[N];LD ans,K[G],dp[G][G][2];
struct QAQ{int w,to,next;}a[M<<1];
inline void add(Re x,Re y,Re z){a[++o].w=z,a[o].to=y,a[o].next=head[x],head[x]=o;}
inline void in(Re &x){
int f=0;x=0;char c=getchar();
while(c<'0'||c>'9')f|=c=='-',c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=f?-x:x;
}
struct Dijkstra{
struct QWQ{int x,d;inline bool operator<(QWQ O)const{return d>O.d;};};
int pan[N],dis[N];priority_queue<QWQ>Q;
inline void dijkstra(Re st){
for(Re i=0;i<=n;++i)dis[i]=inf;
Q.push((QWQ){st,dis[st]=0});
while(!Q.empty()){
Re x=Q.top().x;Q.pop();
if(pan[x])continue;
pan[x]=1;
for(Re i=head[x],to;i;i=a[i].next)
if(dis[to=a[i].to]>dis[x]+a[i].w)
Q.push((QWQ){to,dis[to]=dis[x]+a[i].w});
}
}
}T1[N];
inline int dis(Re x,Re y){return T1[x].dis[y];}
int main(){
// freopen("classroom.in","r",stdin);
// freopen("classroom.out","w",stdout);
in(ng),in(mg),in(n),in(m);
for(Re i=1;i<=ng;++i)in(A[i][0]);
for(Re i=1;i<=ng;++i)in(A[i][1]);
for(Re i=1;i<=ng;++i){
scanf("%lf",&K[i]);
// flagT2&=(K[i]==1.0);
}
while(m--)in(x),in(y),in(z),add(x,y,z),add(y,x,z);
if(ng==1){
printf("0.00");
fclose(stdin);
fclose(stdout);
return 0;
}
for(Re i=1;i<=n;++i)T1[i].dijkstra(i);
// if(flagT2){//特殊性質: K[i]全部等於1
// for(Re k=0;k<=mg;++k)
// for(Re i=0;i<=ng;++i)
// dp[k][i][0]=dp[k][i][1]=inf;
// dp[0][1][0]=0;
// for(Re i=2;i<=ng;++i)dp[0][i][0]=dp[0][i-1][0]+dis(A[i-1][0],A[i][0]);
// for(Re k=1;k<=mg;++k)
// for(Re i=k;i<=ng;++i){
// dp[k][i][0]=min(dp[k][i-1][0]+dis(A[i-1][0],A[i][0]),dp[k][i-1][1]+dis(A[i-1][1],A[i][0]));
// dp[k][i][1]=min(dp[k-1][i-1][0]+dis(A[i-1][0],A[i][1]),dp[k-1][i-1][1]+dis(A[i-1][1],A[i][1]));
// }
// ans=inf;
// for(Re k=0;k<=mg;++k)ans=min(ans,min(dp[k][ng][0],dp[k][ng][1]));
// printf("%.2lf\n",ans);
// }
// else{
for(Re k=0;k<=mg;++k)
for(Re i=1;i<=ng;++i)//dp[0][][]手動初始化,不用設inf
dp[k][i][0]=dp[k][i][1]=1000000000.0;
dp[1][1][1]=dp[0][1][0]=0;//i=1的情況手動初始化
for(Re i=2;i<=ng;++i)dp[0][i][0]=dp[0][i-1][0]+(LD)dis(A[i-1][0],A[i][0]);
for(Re i=2;i<=ng;++i)//從i=2開始跑
for(Re k=1;k<=mg&&k<=i;++k){
dp[k][i][0]=min(
dp[k][i-1][0]
+(LD)dis(A[i-1][0],A[i][0]),
dp[k][i-1][1]
+(LD)dis(A[i-1][1],A[i][0])*K[i-1]
+(LD)dis(A[i-1][0],A[i][0])*(1.0-K[i-1])
);
dp[k][i][1]=min(
dp[k-1][i-1][0]
+(LD)dis(A[i-1][0],A[i][1])*K[i]
+(LD)dis(A[i-1][0],A[i][0])*(1.0-K[i]),
dp[k-1][i-1][1]
+(LD)dis(A[i-1][1],A[i][1])*K[i-1]*K[i]
+(LD)dis(A[i-1][0],A[i][1])*(1.0-K[i-1])*K[i]
+(LD)dis(A[i-1][1],A[i][0])*K[i-1]*(1.0-K[i])
+(LD)dis(A[i-1][0],A[i][0])*(1.0-K[i-1])*(1.0-K[i])
);
}
ans=dp[0][ng][0];
for(Re k=1;k<=mg;++k)ans=min(ans,min(dp[k][ng][0],dp[k][ng][1]));
printf("%.2lf\n",ans);
// }
fclose(stdin);
fclose(stdout);
return 0;
}
【Day2】
【T1】
【題目描述】
給出 \(T\) \((T \leqslant 10^4)\) 組數據和一個整數 \(K\) \((K \leqslant 21)\),每組數據給出兩個整數 \(n,m\) \((n,m \leqslant 2000)\),求滿足 \(C^{j}_{i}\) 能被 \(K\) 整除的 \(i,j\) \((i \in [1,n],j\in[1,min(i,m)])\) 對數。
【分析】
組合數遞推公式為 \(C^{m}_{n}=C^{m}_{n-1}+C^{m-1}_{n-1}\),先 \(n^2\) 預處理一下,又由於 \(K\) 是固定的,所以在遞推的時候順帶取個膜,后面就直接判斷 \(C^{j}_{i}\) 是否為 \(0\)。
查詢時 \(n^2\) 暴力掃描可以得 \(90\) 分(送分題就是不一樣,暴力給 \(90\)),考慮用矩陣前綴和優化:
\(S[j][i]=S[j\!-\!1][i]+S[j][i\!-\!1]\!-\!S[j\!-\!1][i\!-\!1]+(C[j][i]==0)\)
但是當 \(j==i\) 時,使用到的 \(S[j][i-1]\) 在上一層循環中沒有被更新到,所以在遞推求 \(S\) 的時候枚舉循環是允許 \(j>i\) 的(也可以直接特判 \(S[j][i]=S[j\!-\!1][i]+(C[j][i]==0)\))。
時間復雜度為:\(O(n^2+T)\) 。
【Code】
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#define Re register int
using namespace std;
const int N=2003;
int x,y,T,K,C[N][N],S[N][N];
inline void in(Re &x){
int f=0;x=0;char c=getchar();
while(c<'0'||c>'9')f|=c=='-',c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=f?-x:x;
}
int main(){
// freopen("problem.in","r",stdin);
// freopen("problem.out","w",stdout);
in(T),in(K);
for(Re i=1;i<=2000;++i)C[0][i]=C[i][i]=1;
for(Re i=2;i<=2000;++i)
for(Re j=1;j<=i;++j)
C[j][i]=(C[j][i-1]+C[j-1][i-1])%K;
for(Re i=1;i<=2000;++i)
for(Re j=1;j<=2000;++j)
S[j][i]=S[j-1][i]+S[j][i-1]-S[j-1][i-1]+(j<=i&&C[j][i]==0);
while(T--)in(x),in(y),printf("%d\n",S[y][x]);
fclose(stdin);
fclose(stdout);
return 0;
}
【T2】
【題目描述】
給出 \(n\) \((n \leqslant 10^5)\) 只蚯蚓的長度 \(x_{i}\),每秒選出其中最長的一只將其砍為 \(\lfloor px_{i} \rfloor\) \((0<p<1)\) 和 \(x_{i}-\lfloor px_{i}\rfloor\) 兩只,隨后所有蚯蚓都會伸長 \(q\) \((0 \leqslant q \leqslant 200)\)。輸出第 \(t,2t,3t..\lfloor\frac{m}{t}\rfloor\) \((1 \leqslant t \leqslant 71)\) 秒被切斷的蚯蚓(在被切之前)長度以及 \(m\) \((0 \leqslant m \leqslant 7*10^6)\) 秒之后第 \(t,2t,3t...\lfloor\frac{n+m}{t}\rfloor\) 長的蚯蚓長度。
【分析】
由於每次砍出來的兩半長度一定小於等於砍之前,所以每次選出來要砍的蚯蚓長度 \(x\) 一定是單調不上升的,那么 \(\lfloor px\rfloor\) 和 \(x-\lfloor px\rfloor\) 也應該是單調不上升的,直接開三個隊列模擬一下就好了。
還要解決每秒長度的增加量 \(q\),老老實實的加是肯定不行的,龐大的數據量用 \(sort\) 都可能 \(TLE\),更別說什么數據結構之類的東西了。但可以記錄一個 \(dlt\),表示當前已經欠了 \(dlt\) 沒有加,而元素出隊時只要加上這個值就可以表示真實長度。
【Code】
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<queue>
#define LD double
#define Re register int
using namespace std;
const int N=1e5+3,M=7e6+3;
int x,n,m,q,u,v,t,tmp,a[N],H[3]={1,1,1},T[3],Q[3][N+M];LD p;
inline void in(Re &x){
int f=0;x=0;char c=getchar();
while(c<'0'||c>'9')f|=c=='-',c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=f?-x:x;
}
inline int Top(){//找到三個隊列中最長的一個出隊
Re p,ans=-2e9;
for(Re i=0;i<3;++i)
if(H[i]<=T[i]&&Q[i][H[i]]>ans)ans=Q[p=i][H[i]];
++H[p];
return ans;
}
int main(){
// freopen("earthworm.in","r",stdin);
// freopen("earthworm.out","w",stdout);
in(n),in(m),in(q),in(u),in(v),in(t);p=(LD)u/v;
for(Re i=1;i<=n;++i)in(a[i]),a[i]*=-1;
sort(a+1,a+n+1);
for(Re i=1;i<=n;++i)Q[0][++T[0]]=-a[i];//第一波蚯蚓入隊
for(Re o=1;o<=m;++o){
Re x=Top()+tmp;//獲取真實長度要加上懶標記
Re A=x*p,B=x-A;
if(o%t==0)printf("%d ",x);
tmp+=q;//修改懶標記
Q[1][++T[1]]=A-tmp;//入隊時要減去懶標記
Q[2][++T[2]]=B-tmp;//同上
}
puts("");
for(Re o=1;o<=n+m;++o){
x=Top();
if(o%t==0)printf("%d ",x+tmp);//輸出真實長度
}
fclose(stdin);
fclose(stdout);
return 0;
}
【T3】
【題目描述】
分別給出平面上 \(n\) \((n \leqslant 18)\) 只小豬的坐標 \((x_{i},y_{i})\) \((0 < x,y < 10)\),有一架彈弓位於 \((0,0)\) 處,每次 \(\text{Kiana}\)(%%%)可以發射一只飛行軌跡為 \(y=ax^2+bx\) \((\)需滿足 \(a<0)\) 的小鳥。
一共有 \(T\) \((T \leqslant 30)\) 組數據,求每組數據最少需要多少只小鳥才能打完所有小豬。
【分析】
感受到了來自出題人深深的惡意,以后都不敢直視這個游戲了。
\(n \leqslant 18\),不是 \(dfs\) 就是狀壓,事實證明兩種思路都可以過。個人認為狀壓更好想,寫起來也比較簡單。
首先要 \(n^3\) 預處理出過任意兩點 \(i,j\)(或只過一點)的拋物線可以穿過的小豬有哪些,並用一個整數 \(line[i][j]\) 來表示這個狀態。注意特判:僅當求出的一元二次函數 \(a<0\) 時才合法。
用 \(dp[j]\) 表示小豬狀態為 \(j\) 時的最小答案,則轉移方程可表示為:\(dp[j|line[i][k]]=min\{dp[j|line[i][k]],dp[j]+1\}\) 。
注意:不能寫 \(dp[j]=min\{dp[j\) ^ \(line[i][k]]+1\}\),因為一頭豬是可以被多條拋物線覆蓋的,而這種寫法是不能互相覆蓋的情況(但貌似數據水可以過)。
暴力枚舉 \(i,j,k\),時間復雜度為:\(O(Tn^{2}2^{n})\) 。
對於這道題,我表示很 \(angry\),因為模擬考試時抽風成為常態的 \(Cena\) 又把我搞爆蛋了,而且爆的還是 \(long\) \(double\) 讀入的 \(\color{red}{C++標准寫法}\),真的是無語了。。。
【Code】
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#define LD long double
#define Re register int
using namespace std;
const int N=20,M=262150;
int n,T,V,HYJ,dp[M],line[N][N];LD eps=1e-10;//到底是eps還是esp呢?我說不清
struct Poi{LD x,y;inline bool operator<(Poi O)const{return x!=O.x?x<O.x:y<O.y;};}P[N];
struct Line{
LD a,b;
inline bool judge(Poi O)const{//判斷某點是否在某拋物線上
LD x=a*O.x*O.x+b*O.x,y=O.y;
return (x-y>=-eps&&x-y<=eps);//比較大小時的精度處理
}
};
inline void in(Re &x){
int f=0;x=0;char c=getchar();
while(c<'0'||c>'9')f|=c=='-',c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=f?-x:x;
}
inline Line Get_line(Poi a,Poi b){//手推公式,初中數學
Line ans;
ans.a=(a.y/a.x-b.y/b.x)/(a.x-b.x);
ans.b=a.y/a.x-ans.a*a.x;
return ans;
}
inline void print(Re j){for(Re k=n;k>=1;--k)printf("%d",(j&(1<<k-1))>0);}
int main(){
// freopen("angrybirds.in","r",stdin);
// freopen("angrybirds.out","w",stdout);
in(T);
while(T--){
in(n),in(HYJ),V=(1<<n)-1;
for(Re i=1;i<=n;++i)scanf("%Lf%Lf",&P[i].x,&P[i].y);
//我偏要用scanf讀long double,我不信你noi linux能卡標准寫法
sort(P+1,P+n+1);
memset(line,0,sizeof(line));
for(Re i=1;i<=n;++i)line[i][i]=1<<i-1;//單獨處理只經過一個點的拋物線
for(Re i=1;i<=n;++i)
for(Re j=1;j<i;++j){
Line L=Get_line(P[i],P[j]);
if(L.a>=0)continue;//題目要求a<0
for(Re k=1;k<=n;++k)
if(L.judge(P[k]))line[i][j]|=1<<k-1;
line[j][i]=line[i][j];
}
memset(dp,127,sizeof(dp));
dp[0]=0;
for(Re i=1;i<=n;++i)
for(Re j=0;j<=V;++j)
for(Re k=1;k<=n;++k)
dp[j|line[i][k]]=min(dp[j|line[i][k]],dp[j]+1);
printf("%d\n",dp[V]);
}
fclose(stdin);
fclose(stdout);
return 0;
}