• 第一届『Citric杯』NOIP提高组模拟赛 题解


    【官方题解】第一届『Citric杯』NOIP提高组模拟赛 题解

    第一题 柠檬超市

    这题是本次模拟赛的送分题。做法显然。
    但是注意此题有一个陷阱:
    注意W和C的规模都是10^9,所以如果直接用double来存储性价比会挂精度(999999999/1000000000和999999998/999999999的差距只有10^-18,这么微小的差距double是分辨不出来的)
    正确做法是,把除法的除数移项,转化成乘法,然后用int64或long long。
    另外一个讨巧的方法是用long double或extended来存储性价比,因为long double/extended的有效数字比double更多,所以勉强能不挂精度。
    很不幸,如果挂精度的话就只有20分了。

    需要的知识:模拟
    代码长度 小于0.5KB.

    第二题 柠檬的坦克游戏

    此题20%数据的做法显然,直接照着题目要求模拟即可,O(N^3)

    此题40%数据做法也比较简单,有两个做法:
    1.对20%的做法进行改进,预先对所有的武器按D值排序,这样查找有没有更优的武器时直接查找D值比它大的就行了,时间复杂度可以证明被均摊到了O(N^2)。
    2.依然预先对所有武器按D值从小到大排序,然后每次扫一遍,用一个单调栈维护即可,只要遇到R值比栈顶大就弹栈,否则入栈。扫一次就能得出一组,也是O(N^2)的。

    此题满分做法是对上一个做法的改进。
    仔细观察题目的流程,我们可以发现一个更优的做法。
    首先我们发现同一组内的武器,按D值从大到小排序后,R值必然也是从小到大的。
    而且我们发现,如果一件武器A的D值比另一件武器B的D值大,那么武器B不会影响到武器A的分组。
    于是考虑把武器按D值从大到小排序,然后逐个加入武器,并为新加入的武器找到合适的组。因为在新加入一件武器之前,我们已经加入了所有D值更大的武器,而剩下的武器都不会影响当前武器的分组,所以现在得出的组号就是最终的组号。
    假设D值最大的前x个武器已经全部加入组,而且现在已经有k组了,定义第i组里最大的R值为maxR(i),那么我们发现maxR(i)必然随i递减。
    我们考虑加入第x+1个武器,我们发现,按照题目要求,这件武器必然会加入到第i组,i是最小的让maxR(i)<D成立的i值。
    于是我们直接用数组存储所有组的maxR,新加入武器时二分查找到加入位置,然后更新对应的那个maxR即可。
    时间复杂度O(NlgN)

    需要的知识: 二分查找
    代码长度 0.5KB ~ 1KB

    第三题 柠檬当上了JC局长!

    此题20%数据做法显然,直接爆搜即可。
    想要得到更高的分数,首先我们要观察一些性质。
    首先,我们发现罪犯的逃跑方式,实际是在给定图的点1的最短路径图上,从根开始,每次随机选择一个孩子移动,移动到叶子后如果还没被抓到就失败了。
    而且,题目保证了从结点1到任意结点的最短路唯一,所以点1的最短路径图实际是一棵树!
    于是,我们可以先做一次点1为源点的dijkstra或直接floyd,把点1的最短路径树建立出来。
    然后问题就变成了,给定一棵树,你可以在树的结点上设置埋伏,以获得最高的抓捕成功几率。
    我们发现一个非常显然的动态规划状态:dp[i][j]在点i为根的子树内共设置j个**,罪犯到达i后能获得的最高成功率。

    那么50%数据的做法就非常显然了。
    因为我们保证了任意一个结点度数不超过3,也就是每个结点最多只有2个孩子(根可能有3个孩子,不过这不会影响复杂度)
    于是我们转移时,暴力枚举根以及其孩子分别分配到了多少个人进行埋伏。时间复杂度是O(N*P^4)

    进一步思考,很容易得出满分做法。
    我们发现,在一个结点上所要进行的分配人力的任务,实际是一个背包。于是在结点上做一个背包即可。
    但是注意这个背包是以当前结点的所有的孩子做背包,然后因为当前节点抓捕失败后才能往孩子走,所以当前结点要枚举埋伏人数,才能得出最终的答案。
    时间复杂度是O(N*P^2)

    需要的知识:最短路径算法、最短路径树的概念、背包DP、树形DP
    代码长度 1KB~2KB

    数据和标程的下载地址: http://pan.baidu.com/share/link?shareid=3747&uk=2249476017

    最后,感谢诸位的参与。感谢Vani,kAc,applepi等人的验题

    PS:以上是sillycross大神的官方题解,他写的是pascal,我等c++支持者便是抓瞎,当然,我真的没看他的代码。因为看了也不懂。

    以下是我自己写的c++的代码,大神不要BS我啊!

    第一题:我这个蒟蒻,一看大水,果断5分钟敲完交了,没想精度问题,果断20分。。

    View Code
     1 #include <cstdio>
     2 #include <cstdlib>
     3 using namespace std;
     4 __int64 ans,n,s,w,c,i,fz=0,fm=1;
     5 void read()
     6 {
     7     scanf("%I64d%I64d",&n,&s);
     8     for(i=1;i<=n;i++)
     9     {
    10         scanf("%I64d%I64d",&w,&c);
    11         if(c<=s)
    12         {
    13             if(fz*c<fm*w)
    14             {
    15                 fz=w;
    16                 fm=c;
    17                 ans=i;
    18             }
    19         }
    20     }
    21     printf("%I64d",ans);
    22 }
    23 int main()
    24 {
    25     read();
    26     system("pause");
    27     return 0;
    28 }

    第二题:我表示我一看n^2的算法了,懒得写了,直接模拟的,rp不错,骗了30分。其实仔细想想nlogn也不是很难,还是做题缺乏深入思考。跪地。

    View Code
     1 #include <cstdio>
     2 #include <cstdlib>
     3 #include <algorithm>
     4 using namespace std;
     5 int n,maxr[100010],cnt,len,num;
     6 struct P
     7 {
     8     int d,r,id,bh;
     9 }p[100010];
    10 bool cmp(const P &x,const P &y)
    11 {
    12     return x.d>y.d;
    13 }
    14 bool cmp2(const P &x,const P &y)
    15 {
    16     return x.id<y.id;
    17 }
    18 void read()
    19 {
    20     scanf("%d",&n);
    21     for(int i=1;i<=n;i++)
    22     {
    23         scanf("%d%d",&p[i].d,&p[i].r); 
    24         p[i].id=i;
    25     }
    26     sort(p+1,p+1+n,cmp);
    27 }
    28 void go()
    29 {
    30     len=1;
    31     maxr[len]=p[1].r;
    32     p[1].bh=1;
    33     for(int i=2;i<=n;i++)
    34     {
    35         if(p[i].r<maxr[len])
    36         {
    37             len++;
    38             maxr[len]=p[i].r;
    39             p[i].bh=len;
    40         }
    41         else 
    42         {
    43             int l=1,r=len;
    44             while(l<=r)
    45             {
    46                 int mid=(l+r)>>1;
    47                 if(maxr[mid]<p[i].r) num=mid,r=mid-1;
    48                 else l=mid+1;
    49             }
    50             maxr[num]=p[i].r;
    51             p[i].bh=num;
    52         }
    53     }
    54     sort(p+1,p+1+n,cmp2);
    55     for(int i=1;i<=n;i++) printf("%d\n",p[i].bh);
    56 }
    57 int main()
    58 {
    59     read();
    60     go();
    61     system("pause");
    62     return 0;
    63 }

    第三题:到晚体觉得肚子疼,于是乎去厕所蹲了一会儿,突然来了灵感,发现这是一棵树,因为周克华走的永远是最短路,而spfa可以记录最短路的前继,利用这个前继来建树,剩下的就是树形DP+背包了。。可惜概率算错了。。就得了30分。。继续跪!

    View Code
     1 #include <cstdio>
     2 #include <cstring>
     3 #include <cstdlib>
     4 #include <iostream>
     5 using namespace std;
     6 int n,m,cnt=1,a,b,c,start,h,t,head[220],next[20020],to[20020],len[20020],s,dis[220],que[500200];
     7 double dp[220][220],jl[220][220],f[220];
     8 bool vis[220];
     9 int pre[220],son[220];
    10 void add(int v,int u,int w)
    11 {
    12     len[cnt]=w; to[cnt]=u; next[cnt]=head[v]; head[v]=cnt++;
    13 }
    14 void read()
    15 {
    16     scanf("%d%d",&n,&m);
    17     for(int i=1;i<=m;i++)
    18     {
    19         scanf("%d%d%d",&a,&b,&c);
    20         add(a,b,c); add(b,a,c);
    21     }
    22     scanf("%d",&s);
    23     for(int i=1;i<=n;i++)
    24         for(int j=1;j<=s;j++)
    25             scanf("%lf",&jl[i][j]);
    26 }
    27 void spfa()
    28 {
    29     start=1;
    30     memset(dis,0x3f,sizeof dis);
    31     h=1;t=2;
    32     que[1]=start;
    33     vis[start]=true;
    34     dis[start]=0;
    35     while(h<t)
    36     {
    37         int sta=que[h++];
    38         vis[sta]=false;
    39         for(int i=head[sta];i;i=next[i])
    40         {
    41            if(dis[to[i]]>dis[sta]+len[i])
    42            {
    43                dis[to[i]]=dis[sta]+len[i];
    44                pre[to[i]]=sta;
    45                if(vis[to[i]]==false)
    46                {
    47                     vis[to[i]]=true;
    48                     que[t++]=to[i];
    49                }
    50            }
    51         }
    52     }
    53 }
    54 void create()
    55 {
    56     memset(head,0,sizeof head);
    57     cnt=1;
    58     for(int i=2;i<=n;i++)
    59         add(pre[i],i,1);
    60     for(int i=1;i<=n;i++)
    61         for(int j=head[i];j;j=next[j])
    62             son[i]++;
    63 }
    64 void dfs(int u)
    65 {
    66     for(int i=1;i<=s;i++) dp[u][i]=jl[u][i];
    67     if(head[u]==0) return;
    68     
    69     for(int i=head[u];i;i=next[i]) dfs(to[i]);    
    70     for(int i=0;i<=s;i++) f[i]=0.0;
    71     
    72     for(int i=head[u];i;i=next[i])
    73         for(int j=s;j>=0;j--)
    74             for(int k=0;k<=j;k++)
    75                 f[j]=max(f[j],f[j-k]+dp[to[i]][k]);
    76     
    77     for(int i=0;i<=s;i++) f[i]=f[i]/double(son[u]);    
    78     for(int i=0;i<=s;i++)
    79         for(int j=0;j+i<=s;j++)
    80             dp[u][i+j]=max(dp[u][i+j],f[j]*(1.0-jl[u][i])+jl[u][i]);
    81 }
    82 void gogo()
    83 {
    84     dfs(1);
    85     printf("%.4lf",dp[1][s]);
    86 }
    87 void go()
    88 {
    89     spfa();
    90     create();
    91     gogo();
    92 }
    93 int main()
    94 {
    95     read();
    96     go();
    97     return 0;
    98 }

    总体来说这套题的难度还是低于NOIP的,(这么低分,你也好意思说!),应该得到的教训就是,简单题只要做了就必须拿满分!还有就是,思考完善了再动手敲代码!

    没有人能阻止我前进的步伐,除了我自己!
  • 相关阅读:
    盒子!盒子!盒子!
    常用图片加链接文字加链接代码
    滚动跑马灯标记marquee实用代码
    版权所有LIKEWING_柳我借地存个图学习一下
    一个用CSS制作的表单样式
    一段代码学会CSS交集选择器和并集选择器
    菜鸟关于CSS的一点思考
    后代选择器的范例
    利用JavaScript脚本改中的颜色
    JavaScript:在线五子棋盘
  • 原文地址:https://www.cnblogs.com/proverbs/p/2648920.html
Copyright © 2020-2023  润新知