• 【题解】NOIP2016提高组 复赛


    【题解】NOIP2016提高组 复赛

    传送门:


    【Day1】

    玩具谜题 ( ext{[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】

    天天爱跑步 ( ext{[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】

    换教室 ( ext{[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)( ext{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
    ",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
    ",ans);
    //  }
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    

    【Day2】

    【T1】

    组合数问题 ( ext{[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],jin[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
    ",S[y][x]);
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    

    【T2】

    蚯蚓 ( ext{P[2827]})

    【题目描述】

    给出 (n) ((n leqslant 10^5)) 只蚯蚓的长度 (x_{i}),每秒选出其中最长的一只将其砍为 (lfloor px_{i} floor) ((0<p<1))(x_{i}-lfloor px_{i} floor) 两只,随后所有蚯蚓都会伸长 (q) ((0 leqslant q leqslant 200))。输出第 (t,2t,3t..lfloorfrac{m}{t} floor) ((1 leqslant t leqslant 71)) 秒被切断的蚯蚓(在被切之前)长度以及 (m) ((0 leqslant m leqslant 7*10^6)) 秒之后第 (t,2t,3t...lfloorfrac{n+m}{t} floor) 长的蚯蚓长度。

    【分析】

    由于每次砍出来的两半长度一定小于等于砍之前,所以每次选出来要砍的蚯蚓长度 (x) 一定是单调不上升的,那么 (lfloor px floor)(x-lfloor px floor) 也应该是单调不上升的,直接开三个队列模拟一下就好了。

    还要解决每秒长度的增加量 (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】

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

    【题目描述】

    分别给出平面上 (n) ((n leqslant 18)) 只小猪的坐标 ((x_{i},y_{i})) ((0 < x,y < 10)),有一架弹弓位于 ((0,0)) 处,每次 ( ext{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
    ",dp[V]);
        }
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    
  • 相关阅读:
    Notepad++快捷键
    NAT123内网映射端口
    Maven 导出依赖Jar,生成source.jar,javadoc.jar
    自定义javascript log方法
    AmazeUI定制
    模板框架
    jquery checkbox全选 获取值
    首页导航点击A连接跳转并添加上背景色,
    Controller中的方法利用ip跨域访问
    Datatable转成List
  • 原文地址:https://www.cnblogs.com/Xing-Ling/p/11699609.html
Copyright © 2020-2023  润新知