NHOI2016的解题报告

NHOI2016解题报告

解题反思:这次比赛考得非常不好,第三题爆搜打错了,第五题没有想到用斜率来做,本来可以很高分的,下次继续努力。

南海实验中学 105班 区庆亮

第1题  购书(gs)

【题意分析】

  有某人要买n本书。现在有促销活动,每买三本中可以去掉价格最小的一本再计算价钱。问:最合算的价格是多少?

【试题难度】

★☆☆☆☆

【解题思路】

贪心:

因为是取三本中价格最小的一本。所以,只有按照书的价格从大到小,每三本配套的时候,才能使价格最合算。所以,可以得出如下算法:

1,         按书的价格从大到小排序。

2,         每三本配套,计算能省下的价格。(也就是计算价格为3的倍数大的总和)

3,         输出总数-能省下的价格。

但是,值的注意的是,总数需要用long long。在最坏情况下,书的价格全部为100000,n=1000000。那么:价格总和就为(10^5)*(10^6)=10^11,要用longlong。

【时空复杂度】

时间复杂度:排序O(nlogn),计算价格O(n),总时间复杂度O(nlogn)。

空间复杂度:O(n)。

【解题反思】

做题时应该注意会不会超过int范围。写程序前,要看一看数据范围。

【程序】

#include<cstdio>

#include <iostream>                                                     

#include<algorithm>

 

using namespacestd;

 

int n,a[100011];

long long sum;//注意用long long

 

bool cmp(int x,inty){return x>y;}

 

int main()

{

      freopen("gs.in","r",stdin);

      freopen("gs.out","w",stdout);

     

      scanf("%d",&n);

      for(int i=1;i<=n;i++)

      {

           scanf("%d",&a[i]);

           sum+=a[i];//计算总和

      }

      sort(a+1,a+n+1,cmp);//按从大到小排序

      for(int i=3;i<=n;i+=3)

           sum-=a[i];//计算能省的价钱

      cout<<sum;

     

      return 0;

}

第2题  最大最小(zdzx)

【题意分析】

输出l到d中各位数字的和为x的最小与最大的数。

【试题难度】

★☆☆☆☆

【解题思路】

只要纯模拟就可以了。分别顺序和倒叙顺序查找区间[l,d]中的书,只要找到第一个数的个位数字的和为x,直接输出那个数就可以了。

【时空复杂度】

时间复杂度: O(log10(D)*(D-L))

空间复杂度: O(1)

【解题反思】

要尽可能的把程序的复杂度优化,这样才能避免程序因为极端数据而超时。

【程序】

#include <cstdio>

#include <iostream>

using namespace std;

int n,m;

 

int x;

 

inline bool check(int x,int num)//x的个位数字的和是否为num

                         //如是,返回1。否则返回0.

{

    int sum=0;

    while(x>0)

    {

         sum+=x%10;

         x/=10;

    }

    if(sum==num)return 1;

    return 0;

}

 

int main()

{

    freopen("zdzx.in","r",stdin);

    freopen("zdzx.out","w",stdout);

   

    scanf("%d%d%d",&n,&m,&x);

    for(inti=n;i<=m;i++)//顺序查找

    if(check(i,x))

    {

         printf("%d\n",i);

         break;

    }

   

    for(inti=m;i>=n;i--)//倒序查找

    if(check(i,x))

    {

         printf("%d\n",i);

         break;

    }

    return 0;

}

第3题  组合数(zhs)

【题意分析】

从1到n中选取某些数,并加上限制条件,即某两个数不能选。输出方案总数。

【试题难度】

★★★☆☆

【解题思路】

一开始,我还以为是组合数学,但是看到n<=20时,就想到可以用搜索来解决。

首先,我们可以先用邻接矩阵f来存限制条件(当i,j之间有限制条件时,f[i][j]=true)。然后,我们可以在进行搜索。但是,搜索时,要注意:不能把所有的情况搜索出来再进行判断,要符合情况在进行搜索。但是,又要怎样判断呢?我们设当前数列为a,只需要判断当前搜到的数ak与a1~ak-1是否存在限制条件,即f[ak][ ai](0<i<k)都为0,才可以进行搜索。

【时空复杂度】

空间复杂度:O(n^2)

时间复杂度:O(2^n*n)

【解题反思】

    1,假如考试时思绪很乱,就应该在草稿纸上列出所有的思路,记住每一步的细节,在进行编程。

    2,搜索时,不能盲目地进行搜索。应该优化的地方一定要优化,避免出现没有用处的搜索方案,适当地进行剪枝。

【程序】

#include <cstdio>

 

using namespace std;

 

int n,m;

int ans=0;//因为方案总数最多为2^n,不会超过范围

int a[100010];

bool f[410][410];//邻接矩阵

int x,y;

void dfs(int t,int k,int now)//t为选择的个数,k为层数,now为现在的最大值 =a[k-1]+1;

{

    if(k>t)//搜索到一种方案数

    {

        

         ans++;

         return;

        

    }

    bool flag;

    if(k==1)//特殊处理

    {

         for(inti=1;i<=n;i++)

         {

               a[k]= i;

               dfs(t,k+1,i+1);

               a[k]=0;

         }

    }

    else

    for(inti=now;i<=n;i++)

    {

         flag=0;

         for(intj=1;j<k;j++)

               if(f[a[j]][i]==1)//假如不符合条件

               {

                    flag=1;

                    break;

               }

         if(flag)continue;

         a[k] =i;

         dfs(t,k+1,i+1);

         a[k]=0;

    }

}

 

int main()

{

    freopen("zhs.in","r",stdin);

    freopen("zhs.out","w",stdout);

    scanf("%d%d",&n,&m);

    for(inti=1;i<=m;i++)

    {

         scanf("%d%d",&x,&y);

         f[x][y]=f[y][x]=1;

    }

    ans=1;//当i=0时,集合为空

    for(inti=1;i<=n;i++)

         dfs(i,1,1);

    printf("%d",ans);

    return 0;

   

第4题  单词迷(dcm)

【题意分析】

有一个单词,然后把单词的字母打乱,连接到单词的后面。请找出长度最小的这样的单词并输出。如找不到则输出-1。

【试题难度】

★★★☆☆

【解题思路】

假如是连接到单词的后面,那么这个单词的长度必定是这个字符串的约数(不包括len)。然后,我们就可以枚举len所有的因子,再判断一下是否属于这个单词。那么,我们应该怎样判断呢?只需要把每个字符子串进行排序,再比较是否相等就可以了。

因子数最大为loglen,扫描所有整个字符串长度需要O(len),桶排时间总和约为O(len),所以,总时间复杂度为O(lenloglen ),不超时。

【时空复杂度】

时间复杂度:O(factor(len)*len)。//factor(len)指len因子的个数

空间复杂度:O(len)。

【解题反思】

    尽量不要让程序进行没有用处的运算。需要想好程序的步骤再进行编程。

【程序】

#include<cstdio>

#include<cstring>

#include<string>

#include<iostream>

 

using namespacestd;

 

char sts[100010];

string s;

int n;

bool flag;

inline stringchange(string s)//排序 ,只要O(n)的时间

{

      int a[30]={0},len=s.length();

      for(int i=0;i<len;i++) a[s[i]-96]++;//桶排

      string st;

      for(int i=1;i<30;i++)

      for(int j=1;j<=a[i];j++)

      st+=char(i+96);

      return st;

}

 

int main()

{

      scanf("%s",sts);

      s=sts;

      n=s.length();

      flag=0;

      for(int i=1;i<n;i++)//特殊判断

      {

           if(s[i]!=s[0])

           {

                 flag=1; break;

           }

      }

      if(!flag)//假如全是一样

      {

           printf("%c",s[0]);

           return 0;

      }

      for(int i=2;i<n;i++)

      if(n%i==0)//枚举所有的因子

      {

           flag=1;

           string now=change(s.substr(0,i));//根单词

           for(int j=i;j<n;j+=i)

           {

                 if(now!=change(s.substr(j,i)))//假如不一样

                 {

                      flag=0;

                      break;

                 }

           }

           if(flag)//符合条件

           {

                 cout << s.substr(0,i);

                 return 0;

           }

      }

      printf("-1");//无解

      return 0;

}

第5题  线段(xd)

【题意分析】

有n在x轴上方的线段,现在要依次下落到x轴下方,且下落过程中不能与另外的线段相交,请求出一个下落过程。

【试题难度】

★★★★☆

【解题思路】

假如,线段x在下落过程中被条线段y挡住,那么,y就必须先输出。也就是说,不完成y就没法完成x,所以,我们就可以马上想到用拓扑排序!

我们可以记num[i]为有多少条线段挡住了线段i。每次找到一个入度为0的线段(即没有任何线段能挡住它)并输出,然后,再判断它挡住了什么线段,并把num[那一条线段]的值减1。剩下,只需要判断,某一条线是否挡住了另外一条就可以了:

考虑两条线段,我们可以先把完全不重合的情况去掉。

之后,我们设上面的线段为x,下面的为y,就可以把它们分成两种情况:一种x在y左边,另一种在右边,然后,我们就可以计算x的斜率(在x的线段中,横坐标每增加1时,纵坐标增加的数)。然后,我们就可以计算出y线段上方的点的纵坐标。这样我们就可以O(1)判断了。

【时空复杂度】

时间复杂度:O(n2)。

空间复杂度:O(n2)。

【解题反思】

    在做这些题目时,要考虑运用几何巧妙地判断,并且能马上想出算法,完成题目。

【程序】

#include<cstdio>

#include<iostream>

#include<vector>

#include<map>

#include<algorithm>

#include<cmath>

using namespacestd;

 

struct data{

      int x1,y1,x2,y2;

}f[5010];//记录线段

 

bool id[5010];//是否已被删除

bool bo;

int num[5010];//阻挡线段的个数

int n;

 

inline boolpd(data x,data y)//y是否挡住x

{

 

      bool

       boo1=y.x1>=x.x1 && y.x1<=x.x2,

       boo2=y.x2>=x.x1 && y.x2<=x.x2,

       boo3=x.x1>=y.x1 && x.x1<=y.x2,

       boo4=x.x2>=y.x1 && x.x2<=y.x2;

     

      bo=0;

      if(!boo1 && !boo2)

      {

           if(!boo3 && !boo4) return 0;

           else//需要调换过来

           {

                 data t=x;

                 x=y;

                 y=t;

                 boo1=boo3;boo2=boo4;

                 bo=true;//结果也要相反

           }

      }

      if(x.x1==x.x2)//防止下面除以零的情况,需提前处理

      {

           if(boo1)

                 return y.y1>x.y1?0:1;

           if(boo2)

                 return y.y2>x.y1?0:1;

      }

      if(boo1)

      {

           doublek=(x.y1-x.y2)*1.0/(x.x1-x.x2)*1.0;//计算斜率

           returny.y1>x.y1*1.0+(y.x1-x.x1)*k?0:1;//是否在它上方

      }

      else if(boo2)

      {

           doublek=(x.y1-x.y2)*1.0/(x.x1-x.x2)*1.0;//计算斜率

           return y.y2>x.y1*1.0+(y.x2-x.x1)*k?0:1;//是否在它上方

      }

      return 0;

}

 

int main()

{

      freopen("xd.in","r",stdin);

      freopen("xd.out","w",stdout);

      scanf("%d",&n);

      for(int i=1;i<=n;i++)

      {

           scanf("%d%d%d%d",&f[i].x1,&f[i].y1,&f[i].x2,&f[i].y2);

           if(f[i].x1 > f[i].x2)

           {

                 swap(f[i].x1,f[i].x2);

                 swap(f[i].y1,f[i].y2);

           }

      }

     

      for(int i=1;i<=n;i++)//预处理刚开始的情况

      {

           for(int j=1;j<=n;j++)

           if(i!=j)

           {

                 bool p=pd(f[i],f[j]);

                 if(bo) p=p?0:1;

                 if(p)

                 {

                      num[i]++;

                 }

           }

      }

//拓扑排序

      int j;

      for(int i=1;i<=n;i++)

      {

          

           for(j=1;j<=n;j++)

           if( (!id[j]) && num[j]==0)//找到一个num值为0的线段(入度为0),即下面没有任何线段阻挡

           {

                 break;

           }

           printf("%d ",j);

           id[j]=1;//删除

           for(int k=1;k<=n;k++)//把它阻挡的线段的num的个数-1

           if(!id[k])

           {

                 bool p=pd(f[k],f[j]);

                 if(bo) p=p?0:1;

                 if(p) num[k]--;

           }

      }

     

      return 0;

}

第6题  方案数(fas)

【题意分析】

有n个人,取红蓝2 种球。限制:

 1,每个人最少取一个球,只能取相同颜色的球,第 i个人最多取ai个红球,bi个蓝球。

2,取红球数的人不少于c个。

有q次询问,每次修改某个人的ai,bi限制,输出当前条件下的可能方案数模10007。

【试题难度】

★★★★★

【解题思路】

这题很容易想到递推,设f(i,k)为前i个有k个人选红球的方案数,那么,f(i,k) = f(i-1,k-1)*ai + f(i-1,k)*bi。由于选红球的人数不小于c个,所以,总方案数为∑f(n,i)(c<=i<=n)。

    这样的话会超时,然后我们也可以很容易就想到用胜者树来高效维护。这样,就可以在规定时间内解决问题了。

【时空复杂度】

时间复杂度:O(nc2+qlogn*c2)。(时间限制为10秒,不超时)

空间复杂度:O(nlogn*c)。

【解题反思】

1,         要想到用乘法原理来解决问题,在短时间内写出正确的递推式。

2,         学会运用胜者树。

【程序】

#include<cstdio>

#include<iostream>

 

using namespacestd;

 

const int MOD =10007;

 

int f[200010][21];

int a[100010];

int b[100010];

int n,q,c;

inline voidupdate(int x)//暴力修改胜者树

{

      for(int i=0;i<=c;i++) f[x][i] = 0;

      for(int i=0;i<=c;i++)

           for(int j=0;j<=c;j++)

                 f[x][min(i+j,c)] =(f[x][min(i+j,c)] + ( f[x*2][i] * f[x*2+1][j] )) %MOD;

      for(int i=0;i<=c;i++)

      f[x][i] %= MOD;

}

inline voidchange(int x)//限制变了,整棵树也要变

{

      x+=n;

      memset(f[x],0,sizeof(f[x]));

      f[x][0] = b[x-n] % MOD;

      f[x][1] = a[x-n] % MOD;

      for(x/=2;x>0;x/=2)

      {

           update(x);

      }

}

 

int main()

{

      freopen("fas.in","r",stdin);

      freopen("fas.out","w",stdout);

      int p=0;

      scanf("%d%d",&n,&c);

      for(int i=0;i<n;i++)scanf("%d",&a[i]);

      for(int i=0;i<n;i++)scanf("%d",&b[i]);

      for(int i=0;i<n;i++)

      {

           p=i+n;

           f[p][0] = b[i] % MOD;

           f[p][1] = a[i] % MOD;

      }

      for(int i=n-1;i>=1;i--) update(i);//预处理

      scanf("%d",&q);

      for(;q>0;q--)

      {

           scanf("%d",&p); p--;

           scanf("%d%d",&a[p],&b[p]);

           change(p);

           printf("%d\n",f[1][c]);//输出根节点

      }

      return 0;

}

 

转载于:https://www.cnblogs.com/ouqingliang/p/9245314.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值