[竞赛][比赛题解]Noip2016双日游

11.18


联赛前一天,作为强省的弱OIer,第一次参加Noip提高组还是挺紧张的。于是在训练的小黑屋了浪了一整天……


11.19


Day1
因为本土作战,而且家离学校近,所以睡到7:40才起床。
T1 模拟暴力,考试的时候竟然没注意到mogician……
T2 由于做了前几年的联赛,感觉前两道题应该挺水的,结果愣是看了一个多小时想不出正解……只好打80分的暴力。
T3 正解打挂没时间,搜索暴力……


11.20


Day2
T1 O(N2) 预处理后 O(1) 输出
T2 吸取第一天的教训,第二题直接STL堆暴力
T3 打完暴搜后还有一个多小时,开始着手优化搜索……在想记忆化的时候猛然想到可以状压DP……结果被卡精度,又没预处理… O(T2NN3)


11.28


听说零点出成绩,于是兴致冲冲地等到了0:00,结果CCF没上班,网页只有一个空壳…..
下午跑到机房,果然出成绩了……

这里写图片描述

12.8


分数线330……


题解

Day1

T1 toy

直接暴力模拟。 O(N)

#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#define N 100010

using namespace std;

int n,f[N],m,noww;
char namE[N][20];

void reaD(int &x){
    char Ch=getchar();x=0;
    for(;Ch>'9'||Ch<'0';Ch=getchar());
    for(;Ch>='0'&&Ch<='9';x=x*10+Ch-'0',Ch=getchar());
}

void reaDs(char a[]){
    char Ch=getchar();int len=0;
    for(;Ch>'z'||Ch<'a';Ch=getchar());
    for(;Ch>='a'&&Ch<='z';a[++len]=Ch,Ch=getchar());
    a[++len]=0;
}

void M(int &noww){
    if(noww<=0) noww+=n;
    if(noww>n) noww-=n;
}

int main(){
    reaD(n);reaD(m);
    for(int i=1;i<=n;i++)
        reaD(f[i]),reaDs(namE[i]);
    noww=1;
    for(int i=1,LoR,Lo;i<=m;i++){
        reaD(LoR);reaD(Lo);
        if(f[noww]^LoR) noww+=Lo;
        else noww-=Lo;
        M(noww);
    }
    for(int i=1;namE[noww][i]!=0;i++) putchar(namE[noww][i]);
    return 0;
}    

T2 running

根据提供的数据信息可拿80……

对于一个从u到v的人,令 f u, v 的Lca,d(x) x 的深度,F(x) x 的父节点

可以先考虑所有人都从根节点出发所有人都跑到根节点的做法:

  • 对于一个T时刻从 x 节点跑到根节点的人,如果路径上的观察员i满足 T+d(x)d(i)=w(i) ,即 T+d[x]=w[i]+d[i] , 那么观察员 i 可以观察到这个人。

  • 对于一个T时刻从根节点跑到 x 节点的人,如果路径上的观察员i满足 T+d(i)=w(i) ,即 T=w(i)d(i) ,那么观察员 i 就可以观察到这个人

所以我们可以把一个从u v 的人分解:

  • 1个在0时刻从u跑到根节点的人
  • 1个d(u)2d(f)时刻从根节点跑到v的人

    但是这样的话 F(f) -根节点这条路径上的观察员也会观察到这个人,所以可以增加两个玩家:

    • -1个 d(u)d(F(f)) 时刻从 F(f) 跑到根节点的人
    • -1个 d(u)2d(f) 时刻从根节点跑到 v 的人

    然后按照所有人从根节点出发所有人跑到根节点的方法做。

    那么复杂度就集中在求LCA的过程,用树剖或倍增

    O(NlogN+N+M)

    #include <cstdio>
    #define N 300010
    
    int En,n,m;
    int w[N],G[N],Ans[N],F[N],S[N],L[N],GG[N],tt;
    int Top[N],ws[N],Dept[N],B[N],R[N],mi[N],GG1[N];
    
    struct timble{
        int t,nx,w;
    }Tb[N<<3];
    
    struct edgee{
        int t,nx;
    }E[N<<1];
    
    inline void reaD(int &x){
        char Ch=getchar();x=0;
        for(;Ch>'9'||Ch<'0';Ch=getchar());
        for(;Ch>='0'&&Ch<='9';x=x*10+Ch-'0',Ch=getchar());
    }
    
    inline void InserT(int x,int y){
        E[++En].t=y;
        E[En].nx=G[x];
        G[x]=En;
    }
    
    inline void clf(){fclose(stdin);fclose(stdout);}
    
    void D1(int x,int f,int d){
        F[x]=f;S[x]=1;
        for(int i=G[x];i;i=E[i].nx)
        if(E[i].t!=f){
            D1(E[i].t,x,d+1);
            if(S[E[i].t]>S[ws[x]]) ws[x]=E[i].t;
            S[x]+=S[E[i].t];
        }
    }
    
    void D2(int x,int dep,int tp){
        Dept[x]=dep;Top[x]=tp;
        if(ws[x]) D2(ws[x],dep+1,tp);
        for(int i=G[x];i;i=E[i].nx)
            if(E[i].t!=F[x]&&E[i].t!=ws[x]) D2(E[i].t,dep+1,E[i].t);
    }
    
    inline void reaDedge(){
        for(int i=1,x,y;i<n;i++){
            reaD(x);reaD(y);
            InserT(x,y);
            InserT(y,x);
        }
        for(int i=1;i<=n;i++) reaD(w[i]);
        D1(1,0,1);D2(1,1,1);
    }
    
    inline int Lca(int x,int y){
        int tp1,tp2;
        while(Top[x]!=Top[y])
            if(Dept[Top[x]]<Dept[Top[y]]) y=F[Top[y]];
            else x=F[Top[x]];
        if(Dept[x]<Dept[y]) return x;
        return y;
    }
    
    inline void InsertT2(int x,int t,int w){
        Tb[++tt].nx=GG[x];
        GG[x]=tt;
        Tb[tt].t=t;
        Tb[tt].w=w;
    }
    
    inline void InsertT1(int x,int t,int w){
        Tb[++tt].nx=GG1[x];
        GG1[x]=tt;
        Tb[tt].t=t;
        Tb[tt].w=w;
    }
    
    void S1(int x,int f){
        int r=B[Dept[x]+w[x]];
        for(int i=GG1[x];i;i=Tb[i].nx)
            B[Tb[i].t]+=Tb[i].w;
        for(int i=G[x];i;i=E[i].nx)
            if(E[i].t!=f) S1(E[i].t,x);
        Ans[x]+=B[Dept[x]+w[x]]-r;
    }
    
    int S2(int x,int f){
        int r;
        if(w[x]-Dept[x]>=0) r=R[w[x]-Dept[x]]; else r=mi[Dept[x]-w[x]];
        for(int i=GG[x];i;i=Tb[i].nx) 
            if(Tb[i].t>=0)R[Tb[i].t]+=Tb[i].w; else mi[-Tb[i].t]+=Tb[i].w;
        for(int i=G[x];i;i=E[i].nx)
            if(E[i].t!=f) S2(E[i].t,x);
        if(w[x]-Dept[x]>=0) Ans[x]+=R[w[x]-Dept[x]]-r;
        else Ans[x]+=mi[Dept[x]-w[x]]-r;
    }
    
    int ww[20],wt;
    
    inline void Pt(int x){
        if(!x){putchar('0');return;}
        while(x)ww[++wt]=x%10,x/=10;
        for(;wt;wt--)putchar(ww[wt]+'0');
    }
    
    int main(){
        reaD(n);reaD(m);
        reaDedge();
        for(int i=1,u,v,f;i<=m;i++){
            reaD(u);reaD(v);
            f=Lca(u,v);
            InsertT1(u,Dept[u],1);
            InsertT1(F[f],Dept[u],-1);
            InsertT2(f,Dept[u]-Dept[f]*2,-1);
            InsertT2(v,Dept[u]-2*Dept[f],1);
        }
        S1(1,0);S2(1,0);
        for(int i=1;i<n;i++) Pt(Ans[i]),putchar(' ');
        return Pt(Ans[n]),0;
    }

    T3 classroom

    概率DP
    f[i][j][k] 表示前 i 节课,申请了换j节课, k=1 表示第i节课申请了换课, k=0 表示没有。

    g(i,j) 表示 i 教室与j教室的最短距离

    那么对于第 i 节课,如果不申请换课:

    • 若第i1节课没有申请换课, f[i][j][0]=f[i1][j][0]+g(c[i1],c[i])

    • 若第 i1 节课申请了换课,那么
      – 有 k[i1] 的概率申请成功,要消耗 g(d[i1],c[i]) 的体力
      – 有 (1k[i1]) 的概率申请失败,要消耗 g(c[i1],c[i]) 的体力
      所以期望消耗 k[i1]g(d[i1],c[i])+(1k[i1])g(c[i1],c[i]) 的体力

    如果申请换课,同意按照上面的方法分类,只不过要同时考虑第 i 节课和第i1节课的申请成功的情况。

    计算两个教室间的最短路程,因为教室数比较小,可以floyd

    O(v3+nm)

    #include <cstdio>
    #include <iostream>
    #include <cstring>
    #include <string>
    #include <algorithm>
    #include <queue>
    #define N 2010
    #define M 310
    
    using namespace std;
    
    typedef long long ll;
    
    int n,m,v,e,C[N],D[N],w[N];
    double K[N],Ans,f[2][N][2];
    ll d[M][M];
    
    inline void reaD(int &x){
        char Ch=getchar();x=0;
        for(;Ch>'9'||Ch<'0';Ch=getchar());
        for(;Ch>='0'&&Ch<='9';x=x*10+Ch-'0',Ch=getchar());
    }
    
    inline int min(const int &a,const int &b){
        return a<b?a:b;
    }
    
    inline double min(double a,double b,double c){
        return min(min(a,b),c);
    }
    
    int main(){
        reaD(n);reaD(m);reaD(v);reaD(e);
        for(int i=1;i<=v;i++){
            for(int j=1;j<=v;j++) d[i][j]=1<<30;
            d[i][i]=0;
        }
        for(int i=1;i<=n;i++) reaD(C[i]);
        for(int i=1;i<=n;i++) reaD(D[i]);
        for(int i=1;i<=n;i++){
            scanf("%lf",&K[i]);
        }
        for(int i=1,x,y,z;i<=e;i++){
            reaD(x);reaD(y);reaD(z);
            d[x][y]=d[y][x]=min(d[x][y],z);
        }
        for(int k=1;k<=v;k++)
            for(int i=1;i<=v;i++)
                for(int j=1;j<=v;j++)
                    if(d[i][j]>d[i][k]+d[k][j]) d[i][j]=d[i][k]+d[k][j];
        memset(f,0x7F,sizeof(f));
        f[1][1][1]=f[1][0][0]=0;
        for(int i=2;i<=n;i++)
            for(int j=0;j<=i&&j<=m;j++){
                f[i&1][j][0]=min(f[i&1^1][j][0]+d[C[i-1]][C[i]],f[i&1^1][j][1]+d[D[i-1]][C[i]]*K[i-1]+d[C[i-1]][C[i]]*(1-K[i-1]));
                if(j) f[i&1][j][1]=min(f[i&1^1][j-1][0]+d[C[i-1]][D[i]]*K[i]+d[C[i-1]][C[i]]*(1-K[i]),
                                     f[i&1^1][j-1][1]+d[D[i-1]][D[i]]*K[i]*K[i-1]+d[D[i-1]][C[i]]*K[i-1]*(1-K[i])+d[C[i-1]][D[i]]*K[i]*(1-K[i-1])+d[C[i-1]][C[i]]*(1-K[i])*(1-K[i-1]));
            }
        Ans=1<<30;
        for(int i=0;i<=m;i++) Ans=min(Ans,f[n&1][i][0],f[n&1][i][1]);
        printf("%.2lf",Ans);
        return 0;
    }

    Day2

    T1 problem

    根据 C(i,j)=C(i1,j1)+C(i,j1) 预处理后输出

    但是因为 C(i,j) 会很大,所已预处理的时候就可以对 C(i,j) 取模,之后判断是不是为零就行了

    O(n2+T)

    #include <cstdio>
    #define N 2010
    
    int A[N][N],n,m,k,t,Ans[N][N];
    
    void reaD(int &x){
        char Ch=getchar();x=0;
        for(;Ch>'9'||Ch<'0';Ch=getchar());
        for(;Ch>='0'&&Ch<='9';x=x*10+Ch-'0',Ch=getchar());
    }
    
    int w[20],wt;
    
    void Pt(int x){
        if(!x){putchar('0');putchar('\n');return ;}
        wt=0;while(x)w[++wt]=x%10,x/=10;
        for(;wt;wt--)putchar(w[wt]+'0');putchar('\n');
    }
    
    int main(){
        reaD(t);reaD(k);
        for(int i=0;i<=2000;i++) A[i][0]=1;A[1][1]=1;
        for(int i=2;i<=2000;i++){
            for(int j=1;j<=i;j++){
                if((A[i][j]=A[i-1][j-1]+A[i-1][j])>=k) A[i][j]-=k;
            }
        }
        for(int i=1;i<=2000;i++)for(int j=i+1;j<=2000;j++) A[i][j]=-1;
        for(int i=1;i<=2000;i++)
            for(int j=1;j<=2000;j++){
                Ans[i][j]=Ans[i-1][j]+Ans[i][j-1]-Ans[i-1][j-1]+(A[i][j]==0);
            }
        while(t--){
            reaD(n);reaD(m);
            Pt(Ans[n][m]);
        }
        return 0;
    }
    

    T2 earthworm

    可以开三个队列,分别记录初始的蚯蚓和切开后的两断蚯蚓的长度,因为存储初始蚯蚓的队列按照升序排列,所以可以证明另外两个队列也是升序的。

    那么每次找最长的只要比较三个队列的头部就行了。

    至于怎么处理每个单位时间增加的长度,可以开个变量L,L每个单位时间加上q,新增的蚯蚓的长度只要减去q就行了。

    O(nlogn+m)

    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    #include <queue>
    #include <vector>
    #define N 100010
    #define M 7000010
    using namespace std;
    
    int n,t,m,L,u,v,q,A[N],tt,T;
    int B[M],C[M],lb,lc,tb,tc;
    
    inline char NC(){
        static char buf[100000],*p1=buf,*p2=buf;
        if(p2==p1){
            p2=(p1=buf)+fread(buf,1,100000,stdin);
            if(p1==p2) return EOF;
        }
        return *p1++;
    }
    
    void reaD(int &x){
        char Ch=NC();x=0;
        for(;Ch>'9'||Ch<'0';Ch=NC());
        for(;Ch>='0'&&Ch<='9';x=x*10+Ch-'0',Ch=NC());
    }
    
    int w[20],wt;
    
    void Pt(int x){
        if(!x){putchar('0');return ;}
        wt=0;while(x)w[++wt]=x%10,x/=10;
        for(;wt;wt--)putchar(w[wt]+'0');
    }
    
    inline void Cut(int x,int ti){
        if(ti==tt){
            Pt(x+L),tt+=T;
            if(tt<=m) putchar(' ');
        }
        int a=1ll*(x+L)*u/v,b=x+L-a;L+=q;
        B[++tb]=a-L;C[++tc]=b-L;
    }
    
    inline void pt(int x,int ti){
        if(ti==tt){ 
            Pt(x),tt+=T;
            if(tt<=m+n) putchar(' ');
        }
    }
    
    inline bool cmp(const int &a,const int &b){
        return a>b;
    }
    
    int main(){
        reaD(n);reaD(m);reaD(q);reaD(u);reaD(v);reaD(T);tt=T;
        for(int i=1;i<=n;i++) reaD(A[i]);
        sort(A+1,A+1+n,cmp);lb=lc=t=1;
        for(int i=1;i<=m;i++){
            if(t<=n&&(A[t]>=B[lb]||lb>tb)&&(A[t]>=C[lc]||lc>tc)) Cut(A[t++],i);
            else if(lb<=tb&&(B[lb]>=C[lc]||lc>tc)) Cut(B[lb++],i);
            else Cut(C[lc++],i);
        }putchar('\n');
        int i=1;tt=T;
        while(t<=n||lb<=tb||lc<=tc){
            if(t<=n&&(A[t]>=B[lb]||lb>tb)&&(A[t]>=C[lc]||lc>tc)) pt(A[t++]+L,i);
            else if(lb<=tb&&(B[lb]>=C[lc]||lc>tc)) pt(B[lb++]+L,i);
            else pt(C[lc++]+L,i);
            i++;
        }
        return 0;
    }

    T3 angrybirds

    状压DP

    因为只有18只小鸟,可以把状态压缩在2^18的整数内。

    但是这样DP的复杂度就是 O(2nn3)

    所以我们可以枚举两只小鸟,计算以他们的坐标和原点坐标画成的抛物线上有哪些小鸟,也用2^18的整数记录下来。

    这样复杂度就是 O(2nn2)

    总复杂度 0(2nn2T)

    #include <cstdio>
    #include <cstring>
    #include <string>
    #include <iostream>
    
    using namespace std;
    
    int n,m,l,r,mid,v[20],qt,tt,d[20][20],Res[1<<20],w;
    double x[20],y[20];
    
    struct prs{
        double a1,b1,a2,b2;
    }q[30],p[20][20];
    
    double abs(double x){
        if(x<0) return -x;
        return x;
    }
    
    prs Biu(int l,int s){
        prs Re;
        Re.a1=(y[l]*x[s]-y[s]*x[l]),Re.a2=(x[l]*x[l]*x[s]-x[s]*x[s]*x[l]);
        if(Re.a2==0) return Re;
        Re.b1=(y[l]-Re.a1*x[l]*x[l]/Re.a2),Re.b2=x[l];
        return Re;
    }
    
    void work(){
        scanf("%d%d",&n,&m);qt=0;
        for(int i=1;i<=n;i++) scanf("%lf %lf",&x[i],&y[i]),v[i]=0;
        for(int i=1;i<=n;i++)
            for(int j=i+1;j<=n;j++)
            if(x[i]!=x[j]){
                p[i][j]=Biu(i,j);
                d[i][j]=0;
                if(p[i][j].a1*p[i][j].a2>-1e-6) continue;
                for(int k=1;k<=n;k++)
                    if(abs(p[i][j].a1*x[k]*x[k]/p[i][j].a2+p[i][j].b1*x[k]/p[i][j].b2-y[k])<=1e-6) d[i][j]|=1<<k-1;
            }
        memset(Res,0x7F,sizeof(Res));
        Res[0]=0;
        for(int i=0,sx;i<=(1<<n);i++){
            for(int j=1;j<=n;j++){
                Res[i|(1<<(j-1))]=min(Res[i|(1<<(j-1))],Res[i]+1);
            }
            for(int j=1;j<=n;j++)
                if((i&(1<<(j-1)))==0)
                for(int k=j+1;k<=n;k++)
                if(x[j]!=x[k]&&(i&(1<<(k-1)))==0&&p[j][k].a1*p[j][k].a2<0){
                    Res[i|d[j][k]]=min(Res[i|d[j][k]],Res[i]+1);
                }
        }
        printf("%d\n",Res[(1<<n)-1]);
    }
    
    int main(){
        int t;
        scanf("%d",&t);
        while(t--) work();
        return 0;
    }

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值