NOIP2011 Day1,Day2 T1 T2 题解

D1T1:铺地毯(carpet.cpp/c/pas)

【题目分析】

分析题目我们可以发现,本题是一个比较简单的枚举,我们可以从后向前扫描,找到的第一个覆盖这个点的地毯,这个地毯的编号就是最后一个覆盖这个点的地毯的编号。

【程序源代码】

  1. //carpet.cpp by JerryXie   
  2.   
  3. #include<cstdio>   
  4. using namespace std;   
  5. struct carpet   
  6. {   
  7.   int a,b,g,k;   
  8. };   
  9.   
  10. carpet c[10001];   
  11. int main()   
  12. {   
  13.     freopen("carpet.in","r",stdin);   
  14.     freopen("carpet.out","w",stdout);   
  15.     int i,n,x,y,ans=-1;   
  16.     scanf("%d",&n);   
  17.     for(i=1;i<=n;i++)   
  18.       scanf("%d%d%d%d",&c[i].a,&c[i].b,&c[i].g,&c[i].k);   
  19.     scanf("%d%d",&x,&y);   
  20.     for(i=n;i>=1;i--)   
  21.       if(c[i].a<=x && c[i].b<=y && c[i].a+c[i].g>=x && c[i].b+c[i].k>=y) //进行判定    
  22.       {   
  23.         ans=i;   
  24.         break;   
  25.       }   
  26.     printf("%d",ans);   
  27.     return 0;   
  28. }   

 

D1T2:选择客栈(hotel.cpp/c/pas)

【题目分析】

分析题目,我们发现可以通过枚举咖啡厅的位置来从枚举的位置的左右寻找方案。但是这样的算法时间复杂度相当高,n的规模即使很低也难以承受,而且我们观察数据范围可以发现,n达到了20万的数据规模,O(n2)的算法都很难拿到满分。但是,我们可以换一个思路。

我的思路是枚举右侧客栈的位置,寻找中间的咖啡店,通过咖啡店的位置找到咖啡店之前的符合条件的客栈,与之进行配对,之后累加方案数量即可。由于20万的数据规模不能采用O(n2)的算法,我们可以通过以空间换时间的方式降低复杂度,即通过记忆化的方式来预先记录一些数据。

我是这样做的:先用一个数组lastp[i]记录一下i号客栈之前距离它最近的一家p小于等于最低消费的客栈(包括i本身)。然后枚举色调ki,用数组knum[i] 表示i之前(包括i)色调是ki的客栈数量。这样的话逐一扫描客栈,如果此客栈也满足ki的色调,那么就判断它的p是否小于等于旅客所能接受的最低消费,如果是则很明显lastp[i]=i,这样的话i之前的所有色调是ki的客栈都能与它配对,则ans+=knum[lastp[i]]-1(因为knum[lastp[i]]中有1个是i自己,不能配对);如果大于最低消费,则lastp[i]之前的所有色调是ki的客栈可以与它配对,则ans+=knum[lastp[i]]。

【程序源代码】

 
  1. //hotel.cpp by JerryXie   
  2.   
  3. #include<cstdio>   
  4. using namespace std;   
  5. struct hotel //定义结构体用来表示客栈    
  6. {   
  7.        int k,p;   
  8. };   
  9.   
  10. hotel h[200001];   
  11. int lastp[200001],knum[200001];   
  12. int main()   
  13. {   
  14.     freopen("hotel.in","r",stdin);   
  15.     freopen("hotel.out","w",stdout);   
  16.     int i,n,k,p;   
  17.     int ans;   
  18.     int work(int,int,int); //声明求解函数    
  19.     scanf("%d%d%d",&n,&k,&p);   
  20.     for(i=1;i<=n;i++)   
  21.       scanf("%d%d",&h[i].k,&h[i].p);   
  22.     ans=work(n,k,p);   
  23.     printf("%d",ans);   
  24.     return 0;   
  25. }   
  26.   
  27. int work(int n,int k,int p) //求解函数    
  28. {   
  29.     int ki,i,j;   
  30.     int ans=0;   
  31.     for(i=1;i<=n;i++) //计算距离i最近的p小于等于最低消费的客栈编号    
  32.     {   
  33.       for(j=i;j>=1;j--)   
  34.         if(h[j].p<=p)   
  35.         {   
  36.           lastp[i]=j;   
  37.           break;   
  38.         }   
  39.     }   
  40.     for(ki=0;ki<k;ki++) //枚举色调    
  41.     {   
  42.       for(i=1;i<=n;i++) //计算i之前(包括i)符合色调ki的客栈数目    
  43.       {   
  44.         if(h[i].k==ki)   
  45.           knum[i]=knum[i-1]+1;   
  46.         else  
  47.           knum[i]=knum[i-1];   
  48.       }   
  49.       for(i=1;i<=n;i++) //枚举客栈    
  50.       {   
  51.         if(h[i].k!=ki) //如果色调不符合ki,则直接跳过    
  52.           continue;   
  53.         if(h[i].p<=p)  //如果小于等于最低消费    
  54.           ans+=knum[lastp[i]]-1;   
  55.         else  //如果大于最低消费    
  56.           ans+=knum[lastp[i]];   
  57.       }   
  58.     }   
  59.     return ans;   
  60. }   

 

D2T1:计算系数(factor.cpp/c/pas)

【题目分析】

从题目上来分析,很明显能发现此题可以通过使用二项式定理(杨辉三角)进行解决,但是,从数据量来看,a,b∈[0,1000000],中间项将会很大,64位整数都可能无法存下,要用高精度来解决,但是仔细阅读题目就会发现,题目要求答案对10007取模,这样的话可以通过模运算的性质,来避免高精度的使用。

在本题中我们要使用的模运算性质:(a+b)%p=(a%p+b%p)%p,ab%p=(a%p)b,(a*b)%p=[(a%p)*(b%p)]%p

而对二项式定理的应用,则可以通过二维数组递推或者直接应用组合公式进行计算,我们在此使用递推方法。

【程序源代码】

 
  1. //factor.cpp by JerryXie   
  2.   
  3. #include<cstdio>   
  4. #include<cmath>   
  5. using namespace std;   
  6. int tri[1001][1001];  //存储杨辉三角的二维数组    
  7. int main() //主函数    
  8. {   
  9.     freopen("factor.in","r",stdin);   
  10.     freopen("factor.out","w",stdout);   
  11.     int a,b,k,n,m;   
  12.     int i;   
  13.     double res;   
  14.     void triangle(int);  //声明初始化杨辉三角的函数    
  15.     double pownew(int,int); //声明乘方函数    
  16.     double work(int,int,int,int,int); //声明计算函数    
  17.     scanf("%d%d%d%d%d",&a,&b,&k,&n,&m);   
  18.     triangle(k); //用k初始化杨辉三角    
  19.     res=work(a,b,k,n,m);   
  20.     printf("%.0lf",res);   
  21.     return 0;   
  22. }   
  23.   
  24. void triangle(int k) //初始化杨辉三角    
  25. {   
  26.      int i,j;   
  27.      for(i=0;i<=k;i++) //初始化边界    
  28.      {   
  29.        tri[i][0]=1;   
  30.        tri[i][i]=1;   
  31.      }   
  32.      for(i=2;i<=k;i++) //递推求解    
  33.        for(j=1;j<i;j++)   
  34.        {   
  35.          tri[i][j]=tri[i-1][j-1]+tri[i-1][j];   
  36.          tri[i][j]%=10007; //进行取模运算    
  37.        }   
  38.      return;    
  39. }   
  40.   
  41. double pownew(int a,int n) //乘方函数    
  42. {   
  43.     int i,temp;   
  44.     double res=a;   
  45.     if(n==0)   
  46.       return (double)1;   
  47.     else if(n==1)   
  48.       return (double)a;   
  49.     else  
  50.     {   
  51.         temp=a;   
  52.         res=fmod(res,10007);   
  53.         for(i=2;i<=n;i++)   
  54.         {   
  55.           res*=temp;   
  56.           res=fmod(res,10007);   
  57.         }   
  58.         return res;   
  59.     }   
  60. }   
  61.   
  62. double work(int a,int b,int k,int n,int m) //计算函数   
  63. {   
  64.        double res;   
  65.        res=tri[k][n]*pownew(a,n);   
  66.        res=fmod(res,10007);   
  67.        res*=pownew(b,m);   
  68.        res=fmod(res,10007);   
  69.        return res;   
  70. }    

 

D2T2:聪明的质监员(qc.cpp/c/pas)

【题目分析】

从题目上看,很容易可以想到通过枚举W的值逐个计算检验值的大小,从而选出最小值,但是从数据规模上来看如果如此做可能连一半的测试点都无法通过,必须进行优化。我们可以通过二分的思想进行优化。

观察题目,我们可以发现随着W的不断增大,每次选取的矿石数量会减少,即检验值也会减小。所以,我们可以先对w进行排序,然后w的最小值和最大值为区间二分W:

若当前二分值所计算出的Y>S,则代表当前W的取值过小,二分区间的起点变化为当前W,维护当前最小值,继续二分;

若当前二分值所计算出的Y<S,则代表当前W的取值过大,二分区间的终点变化为当前W,维护当前最小值,继续二分;

若当前二分值所计算出的Y=S,则代表当前W取值已经为最优。

而对于区间的问题,我们可以通过建立两个数组a,b,ai表示1-i这些矿石中,wi>W的矿石数量;bi表示1-i这些矿石中,wi>W的矿石的vi值之和。这样,我们可以通过简单的减法来获得任意区间的值。

但是还是有一个问题,观察数据量我们会发现,在逐步累加每个区间累乘积的过程中,累加和在极限数据(即都取到最大值)的情况下会超过64位整数所能存下的最大值(即200000个区间、200000个矿石、最大长度200000、最大价值1000000等)。但是我们也可发现,超过64位整数范围的数与S做减法(大于1019)肯定会比其它取值所计算的绝对值要大,这样的话所有超过64位整数范围的 数我们都可以忽略。因此我们可以进行一个判断,判断64位整数最大值减去当前的乘积是否小于当前累加和,若小于则代表相加后将会溢出,则可以视为累乘和大于s直接退出进行处理。

【程序源代码】

 
  1. //qc.cpp by JerryXie   
  2.   
  3. #include<cstdio>   
  4. #include<climits> //调用long long,int的最大值    
  5. using namespace std;   
  6. struct stone //定义结构体用来表示石头   
  7. {   
  8.        int w,v;   
  9. };   
  10.   
  11. struct section //定义结构体用来表示区间   
  12. {   
  13.        int l,r;   
  14. };   
  15.   
  16. stone sto[200001]; //石头数组    
  17. section sec[200001]; //区间数组    
  18. long long a[200001]={0},b[200001]={0}; //用来存储1-i的数据    
  19.   
  20. int main()   
  21. {   
  22.     freopen("qc.in","r",stdin);   
  23.     freopen("qc.out","w",stdout);   
  24.     int i,n,m,l,r,w,maxw=0,minw=INT_MAX;   
  25.     long long s,tempa,tempb,tempmulti,tempans,abstempans,minys=LONG_LONG_MAX;   
  26.     bool overflowed=false;   
  27.     void count(int,int); //声明处理a,b数组的函数   
  28.     scanf("%d%d%lld",&n,&m,&s);   
  29.     for(i=1;i<=n;i++) //读入+预处理,获得w的最大值和最小值   
  30.     {   
  31.       scanf("%d%d",&sto[i].w,&sto[i].v);   
  32.       if(sto[i].w>maxw)   
  33.         maxw=sto[i].w;   
  34.       if(sto[i].w<minw)   
  35.         minw=sto[i].w;   
  36.     }   
  37.     for(i=1;i<=m;i++)   
  38.       scanf("%d%d",&sec[i].l,&sec[i].r);   
  39.     l=minw;   
  40.     r=maxw;   
  41.     while(l<=r) //二分求解    
  42.     {   
  43.       w=(l+r)/2; //取中点   
  44.       count(w,n); //初始化a,b数组   
  45.       tempans=0;   
  46.       overflowed=false;   
  47.       for(i=1;i<=m;i++) //计算总区间的检验值   
  48.       {   
  49.         tempa=a[sec[i].r]-a[sec[i].l-1];   
  50.         tempb=b[sec[i].r]-b[sec[i].l-1];   
  51.         tempmulti=tempa*tempb;   
  52.         if(LONG_LONG_MAX-tempmulti<tempans) //检验是否溢出,溢出则修改标记    
  53.         {   
  54.           overflowed=true;   
  55.           break;   
  56.         }   
  57.         tempans+=tempmulti;   
  58.       }   
  59.       if(overflowed==true) //如果溢出则按大于s处理    
  60.       {   
  61.         l=w+1;   
  62.         continue;   
  63.       }   
  64.       if(tempans>=s)   
  65.         abstempans=tempans-s;   
  66.       else  
  67.         abstempans=s-tempans;   
  68.       if(minys>abstempans)   
  69.         minys=abstempans;   
  70.       if(tempans==s) //如果检验值等于s则直接为最优解    
  71.       {   
  72.         minys=0;   
  73.         break;   
  74.       }   
  75.       else if(tempans>s) //大于s则修改左端点    
  76.         l=w+1;   
  77.       else //小于s则修改右端点    
  78.         r=w-1;   
  79.     }   
  80.     printf("%lld",minys); //输出结果    
  81.     return 0;   
  82. }   
  83.   
  84. void count(int w,int n) //处理a,b数组    
  85. {   
  86.      int i;   
  87.      if(sto[1].w>=w) //初始化起始项    
  88.      {   
  89.        a[1]=1;   
  90.        b[1]=sto[1].v;   
  91.      }   
  92.      else  
  93.      {   
  94.          a[1]=0;   
  95.          b[1]=0;   
  96.      }   
  97.      for(i=2;i<=n;i++) //逐个初始化    
  98.        if(sto[i].w>=w)   
  99.        {   
  100.          a[i]=a[i-1]+1;   
  101.          b[i]=b[i-1]+sto[i].v;   
  102.        }   
  103.        else  
  104.        {   
  105.            a[i]=a[i-1];   
  106.            b[i]=b[i-1];   
  107.        }   
  108.      return;   
  109. }   

 

欢迎鄙视。

posted @ 2015-09-03 21:00  JerryXie  阅读(210)  评论(0编辑  收藏  举报