【题解】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;
}