NOIP2016提高组解题报告!

经过千辛万苦ATP总算把NOIP2016的六道题都写完了!已经用NOI网站上发布的数据测过所以应该是没问题的啦!

Day1T1

就是模拟啦。注意一下面朝内和面朝外对于左右的区别就好啦。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,now;
struct point{
    char s[20];
    int dir,len;
    void get(){
        char c;
        scanf("%d",&dir);
        c=getchar();len=0;
        while (c<'a'||c>'z') c=getchar();
        while (c<='z'&&c>='a'){
            s[len++]=c;c=getchar();
        }
    }
    void print(){
        for (int i=0;i<len;i++) printf("%c",s[i]);
        printf("\n");
    }
}p[100010];
int main()
{
    freopen("toy.in","r",stdin);
    freopen("toy.out","w",stdout);
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) p[i].get();
    now=1;
    for (int i=1;i<=m;i++){
        int d,k,dlt;
        scanf("%d%d",&d,&k);
        if (p[now].dir==0){
            if (d==1) dlt=1;
            else dlt=-1;
        }else{
            if (d==0) dlt=1;
            else dlt=-1;
        }
        dlt*=k;now=(now+dlt)%n;
        while (now<=0) now+=n;
        while (now>n) now-=n;
    }
    p[now].print();
    return 0;
}

Day1T2

ATP个人觉得这是全场最难想最烧脑最……@#¥%@%……的一道题了。。。光瞪着题解看了半天结果就看懂了它的预处理然后上网各种找然后好不容易把各种做法和成一坨就变成了现在这样。。。

不管怎样先把官方题解贴出来吧。。虽然ATP也不大很懂尤其是它说的什么“我们不妨计算每一个人被观测到几次”什么的。。。

这里写图片描述

诶它说什么“这题专治各种数据结构学傻的人”。。ATP觉得真是一刀戳到脸上=▽=。。。因为ATP当时考试的时候差点写出树套树啊链剖啊这种不靠谱的东西来着。。幸亏仅存的一丝理智让它没有写。。。

好吧其实ATP的做法一开始和题解是一样的,就是把那些曲里拐弯的路径都转化成两类:从根跑到u的和从u跑到根的。这样的话要对每个人记录的信息就变了:要记录它路径上除了根节点以外的另一个端点也就是u;要记录它的开始时间;要记录它是往上跑的还是往下跑的;要记录它的权值。

这样看起来是变麻烦了实际上并没有。。统计的时候,对于每一个观测员,它将会看到u在它子树里的某些人,也就是说统计子树信息的问题。考虑一个在t时刻开始从u跑到根节点的人,如果它能被它祖先上的观测员v看到,那么它一定是在W[v]时刻到达的v。这个时候假设u和v一起往上跑,那么它们一定会同时到达根节点;反过来看,假设v从W[v]时刻出发,如果u和v同时到达了根节点,因为v到根节点那一段路它们是重合的,所以u一定是在W[v]时刻到达的v。那也就是说只需要比较这两个东西到达根节点的时间就可以了!并且要统计v的子树信息的时候,v到达根节点的时间这个东西是固定的,而u到达根节点的时间显然它的值域比较小可以拿一个数组记一下,每次直接访问那个位置就可以了。

然而卡了ATP最长时间的问题是你看如果它直接统计子树信息的话不同的子树之间不是会互相影响的吗。。。如果开很多数组每次统计完子树信息以后再合并那不就炸时间炸空间吗。。。果然ATP足够愚蠢啊其实因为对于每个节点统计它的子树的时候只会访问一个位置,那么只要在dfs它之前记录一下那个位置现在已经有多少值,dfs出来以后统计答案的时候减去那个值就可以了!

不过貌似题解用某种方法把统计子树信息变成了统计祖先信息?ATP也不是很懂。。

然而ATP这题用各种山寨数据还有官方数据都测过了都没问题但是交到BZOJ上就是使劲WA来WA去。。。交到洛谷上竟然给我出了个“UKE”?!鬼知道为啥= =

#include<cstdio>
#include<cstring>
#include<algorithm>
#define Pow 19
using namespace std;
const int base=300000;
int n,m,p[300010],a[600010],nxt[600010],W[300010],deep[300010],cnt[1000010],ans[300010],point[300010],to[1300010];
int f[300010][21],tot,pnxt[1300010],ptot,qutot;
struct query{
    int u,t,tp,val,rec;
    query(int U=0,int T=0,int TP=0,int VAL=0){u=U;t=T;tp=TP;val=VAL;}
}q[1300010];
int comp(query a,query b){
    return a.t<b.t;
}
void add(int x,int y){
    tot++;a[tot]=y;nxt[tot]=p[x];p[x]=tot;
}
void addprs(int u,int num){
    ptot++;to[ptot]=num;pnxt[ptot]=point[u];point[u]=ptot;
}
void dfs(int u){
    deep[u]=deep[f[u][0]]+1;
    for (int i=1;i<=Pow;i++)
      f[u][i]=f[f[u][i-1]][i-1];
    for (int i=p[u];i!=0;i=nxt[i])
      if (a[i]!=f[u][0]){
          f[a[i]][0]=u;
          dfs(a[i]);
      }
}
int find_lca(int x,int y){
    if (deep[x]!=deep[y]){
        if (deep[x]<deep[y]) swap(x,y);
        for (int i=Pow;i>=0;i--)
          if (deep[f[x][i]]>=deep[y])
            x=f[x][i];
    }
    for (int i=Pow;i>=0;i--)
      if (f[x][i]!=f[y][i]){
          x=f[x][i];y=f[y][i];
      }
    while (x!=y){x=f[x][0];y=f[y][0];}
    return x;
}
int dis(int u,int f){return deep[u]-deep[f];}
void search(int u,int dec,int type,int dlt){
    for (int i=point[u];i!=0;i=pnxt[i])
      if (q[to[i]].tp==type)
        cnt[q[to[i]].rec+base]+=q[to[i]].val;
    for (int i=p[u];i!=0;i=nxt[i])
      if (a[i]!=f[u][0]){
          int v=a[i],d;
          d=cnt[W[v]+(deep[v]-1)*dlt+base];
          search(v,d,type,dlt);
      }
    ans[u]+=cnt[W[u]+(deep[u]-1)*dlt+base]-dec;
}
int main()
{
    freopen("running.in","r",stdin);
    freopen("running.out","w",stdout);
    scanf("%d%d",&n,&m);
    for (int i=1;i<n;i++){
        int x,y;scanf("%d%d",&x,&y);
        add(x,y);add(y,x);
    }
    dfs(1);
    for (int i=1;i<=n;i++) scanf("%d",&W[i]);
    for (int i=1;i<=m;i++){
        int s,t,lca;
        scanf("%d%d",&s,&t);
        lca=find_lca(s,t);
        ++qutot;q[qutot]=query(s,0,1,1);
        addprs(s,qutot);
        ++qutot;q[qutot]=query(f[lca][0],dis(s,lca)+1,1,-1);
        addprs(f[lca][0],qutot);
        ++qutot;q[qutot]=query(t,dis(s,lca)-dis(lca,1),2,1);
        addprs(t,qutot);
        ++qutot;q[qutot]=query(lca,dis(s,lca)-dis(lca,1),2,-1);
        addprs(lca,qutot);
    }
    memset(cnt,0,sizeof(cnt));
    for (int i=1;i<=qutot;i++) q[i].rec=q[i].t+deep[q[i].u]-1;
    search(1,0,1,1);
    memset(cnt,0,sizeof(cnt));
    for (int i=1;i<=qutot;i++) q[i].rec=q[i].t;
    search(1,0,2,-1);
    for (int i=1;i<=n;i++){
        printf("%d",ans[i]);
        if (i!=n) printf(" ");
    }
    return 0;
}

Day1T3

比较简单的期望DP啦。。很容易看出来它每条路的期望值是互相不影响的,对于每一条路来说,会影响当前决策的有三个东西:它是第几条路;它之前已经申请了几次;它前面那个端点申请了没有。把这三个东西都记录到状态里,就是f[i][j][0/1]表示当前走到第i个点,已经申请了j次,第i个点有没有申请的总期望,然后递推的时候直接分情况讨论就可以了。。对于连接第i个点和第i+1个点的这条路,就分成都没申请、只有i申请、只有i+1申请和都申请四种情况就可以了。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,v,e,c[2010],d[2010];
double k[2010],f[2010][2010][2],inf,dis[310][310],ans;
double change(int i,double k1,double k2){
    double A,B;
    A=dis[c[i-1]][c[i]]*(1-k1)*(1-k2)+dis[d[i-1]][c[i]]*k1*(1-k2);
    B=dis[c[i-1]][d[i]]*(1-k1)*k2+dis[d[i-1]][d[i]]*k1*k2;
    return A+B;
}
int main()
{
    freopen("classroom.in","r",stdin);
    freopen("classroom.out","w",stdout);
    scanf("%d%d%d%d",&n,&m,&v,&e);
    for (int i=1;i<=n;i++) scanf("%d",&c[i]);
    for (int i=1;i<=n;i++) scanf("%d",&d[i]);
    for (int i=1;i<=n;i++) scanf("%lf",&k[i]);
    memset(dis,127,sizeof(dis));
    inf=dis[0][0];
    for (int i=1;i<=v;i++) dis[i][i]=0;
    for (int i=1;i<=e;i++){
        int x,y,w;
        scanf("%d%d%d",&x,&y,&w);
        dis[x][y]=dis[y][x]=min(dis[x][y],(double)w);
    }
    for (int K=1;K<=v;K++)
      for (int i=1;i<=v;i++)
        for (int j=1;j<=v;j++)
          if (dis[i][K]!=inf&&dis[K][j]!=inf)
            dis[i][j]=min(dis[i][j],dis[i][K]+dis[K][j]);
    memset(f,0x7f,sizeof(f));
    inf=f[0][0][0];
    f[1][0][0]=f[1][1][1]=0;
    for (int i=2;i<=n;i++)
      for (int j=0;j<=m;j++){
          f[i][j][0]=min(f[i-1][j][0]+change(i,0,0),f[i-1][j][1]+change(i,k[i-1],0));
          if (j!=0) f[i][j][1]=min(f[i-1][j-1][0]+change(i,0,k[i]),f[i-1][j-1][1]+change(i,k[i-1],k[i]));
      }
    ans=inf;
    for (int i=0;i<=m;i++)
      ans=min(ans,min(f[n][i][0],f[n][i][1]));
    printf("%.2lf\n",ans);
    return 0;
}

Day2T1

看到k的倍数就可以想到对k取模等于0,然后只要再扫描一遍组合数数组把不是0的那些数包括三角形外面那些数都赋值成一个不为0的数k,然后预处理二维前缀和,那么这一个矩形中0的个数就可以直接算啦。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 2000
using namespace std;
int n,m,k,C[2010][2010],T,s[2010][2010];
void Prepare(){
    for (int i=0;i<=N;i++) C[i][0]=1%k;
    for (int i=1;i<=N;i++)
      for (int j=1;j<=i;j++)
        C[i][j]=(C[i-1][j]+C[i-1][j-1])%k;
    for (int i=0;i<=N;i++)
      for (int j=0;j<=i;j++)
        if (C[i][j]!=0) C[i][j]=-1;
    for (int i=0;i<=N;i++)
      for (int j=i+1;j<=N;j++)
        C[i][j]=-1;
    s[0][0]=C[0][0];
    for (int i=1;i<=N;i++){
        s[0][i]=s[0][i-1]+C[0][i];
        s[i][0]=s[i-1][0]+C[i][0];
    }
    for (int i=1;i<=N;i++)
      for (int j=1;j<=N;j++)
        s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+C[i][j];
}
int main()
{
    freopen("problem.in","r",stdin);
    freopen("problem.out","w",stdout);
    scanf("%d%d",&T,&k);
    Prepare();
    for (int wer=1;wer<=T;wer++){
        int ans;
        scanf("%d%d",&n,&m);
        ans=s[n][m]+((n+1)*(m+1));
        printf("%d\n",ans);
    }
    return 0;
}

Day2T2

这题关键是注意到单调性这种东西。。因为它每次取一个最大值砍成两半,比如说就表示成 A=px B=xA 。它的比例是不变的。而可以发现它取出的那个最大值是单调不上升的。那么如果把A分成一坨,B分成一坨,可以看出来每一坨里面的东西也是单调不上升的。那么只需要另外开两个队列存储A和B,同时把排过序以后的原数组也当成一个队列,每次从三个队头里面比较取最大的就可以了。

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,q,u,v,t,a[100010],Q1[7000010],Q2[7000010],h1,h2,t1,t2,head,tail,dlt;
int comp(int x,int y){return x>y;}
double getlen(double L){
    double U=u,V=v;
    return floor(L*U/V);
}
int getMax(){
    int A,B,C,Max;
    A=B=C=-0x7fffffff;
    if (head!=tail) A=a[head+1];
    if (h1!=t1) B=Q1[h1+1];
    if (h2!=t2) C=Q2[h2+1];
    Max=max(max(A,B),C);
    if (Max==A) ++head;
    else if (Max==B) ++h1;
    else ++h2;
    return Max;
}
int main()
{
    freopen("earthworm.in","r",stdin);
    freopen("earthworm.out","w",stdout);
    scanf("%d%d%d%d%d%d",&n,&m,&q,&u,&v,&t);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    sort(a+1,a+n+1,comp);
    head=0;tail=n;h1=h2=t1=t2=0;
    for (int i=1;i<=m;i++){
        int len=getMax()+dlt,k;
        k=getlen(len);dlt+=q;
        Q1[++t1]=k-dlt;
        Q2[++t2]=len-k-dlt;
        if (i%t==0){
            printf("%d",len);
            if ((i/t+1)*t<=m) printf(" ");
        }
    }
    printf("\n");
    for (int i=1;i<=n+m;i++){
        int len=getMax()+dlt;
        if (i%t==0){
            printf("%d",len);
            if ((i/t+1)*t<=n+m) printf(" ");
        }
    }
    return 0;
}

Day2T3

这就是那个ATP原来以为是爆搜的状压DP。。。而且那个特殊限制啥啥啥其实根本没卵用啊好吧= =预处理抛物线以后可以发现消灭小猪的顺序跟最优解没什么卵关系,并且如果把每一个选择的抛物线当成一个状态,那么最优子结构也很显然。。。用f[S]表示消灭小猪状态为S的时候的最优解,比较显然的做法是枚举状态再枚举某两个小猪组成的抛物线。但是这样 O(T2nn2) 的复杂度是过不了的。而因为顺序没有什么关系,所以如果每次固定消灭当前没有被消灭的编号最小的猪的话就可以少一层循环,就压到了 O(T2nn)

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const double eps=1e-9;
int T,n,m,f[300000];
bool flag;
struct point{
    double x,y;
}p[20];
struct info{
    int rec,c;
}r[20][20];
void calc(double &X,double &Y,point u,point v){
    double A1,A2,B1,B2,C1,C2,tmp1,tmp2;
    A1=u.x*u.x;B1=u.x;C1=u.y;
    A2=v.x*v.x;B2=v.x;C2=v.y;
    tmp1=A1;tmp2=A2;
    A1*=tmp2;B1*=tmp2;C1*=tmp2;
    A2*=tmp1;B2*=tmp1;C2*=tmp1;
    tmp1=B2-B1;tmp2=C2-C1;
    if (tmp1==0) Y=2333;
    else Y=tmp2/tmp1;
    tmp1=C1-B1*Y;
    if (A1==0) X=2333;
    else X=tmp1/A1;
}
bool check(point u,double A,double B){
    return fabs(A*u.x*u.x+B*u.x-u.y)<eps;
}
int comp(int x,int y){return x>y;}
int main()
{
    freopen("angrybirds.in","r",stdin);
    freopen("angrybirds.out","w",stdout);
    scanf("%d",&T);
    for (int wer=1;wer<=T;wer++){
        scanf("%d%d",&n,&m);
        for (int i=1;i<=n;i++)
          scanf("%lf%lf",&p[i].x,&p[i].y);
        memset(r,0,sizeof(r));
        for (int i=1;i<=n;i++){
            r[i][i].rec=(1<<(i-1));
            r[i][i].c=1;
        }
        for (int i=1;i<n;i++)
          for (int j=i+1;j<=n;j++){
              double A,B;
              calc(A,B,p[i],p[j]);
              if (A>=0||B==2333) continue;
              for (int k=1;k<=n;k++)
                if (check(p[k],A,B)){
                    r[i][j].rec|=(1<<(k-1));
                    ++r[i][j].c;
                }
          }
        memset(f,127,sizeof(f));f[0]=0;
        for (int i=0;i<(1<<n);i++){
            int u=n;
            for (int j=1;j<=n;j++)
              if ((i&(1<<(j-1)))==0){
                  u=j;break;
              }
            for (int v=u;v<=n;v++)
              if ((i&(1<<(v-1)))==0){
                  int S=r[u][v].rec;
                  f[i|S]=min(f[i|S],f[i]+1);
              }
        }
        printf("%d\n",f[(1<<n)-1]);
    }
    return 0;
}

哈哈好不容易搞完这玩意儿还是蛮有成就感的_ (:з」∠)_

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值