【题解】NOIP2016提高组 复赛

【题解】NOIP2016提高组 复赛

传送门:


【Day1】

玩具谜题 \(\text{[P1563]}\)

【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】

天天爱跑步 \(\text{[P1600]}\)

【题目描述】

给出一棵 \(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】

换教室 \(\text{[P1850]}\)

【题目描述】

\(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\) 个被选的总路径最小值。

那么有:

\[dp[k][i][0]=min\{dp[k][i\!-\!1][0]+dis(A_{0}[i\!-\!1],A_{0}[i]),dp[k][i\!-\!1][1]+dis(A_{0}[i\!-\!1],A_{0}[i])\} \]

\[dp[k][i][1]=min\{dp[k\!-\!1][i\!-\!1][0]+dis(A_{0}[i\!-\!1],A_{1}[i]),dp[k\!-\!1][i\!-\!1][1]+dis(A_{0}[i\!-\!1],A_{1}[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】

组合数问题 \(\text{[P2822]}\)

【题目描述】

给出 \(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】

蚯蚓 \(\text{P[2827]}\)

【题目描述】

给出 \(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】

愤怒的小鸟 \(\text{P[2831]}\)

【题目描述】

分别给出平面上 \(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;
}
posted @ 2019-10-18 17:33  辰星凌  阅读(810)  评论(0编辑  收藏  举报