NOIP2011 Day1,Day2 T1 T2 题解

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

【题目分析】

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

【程序源代码】

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

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

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

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

欢迎鄙视。

时间: 2024-10-11 15:08:04

NOIP2011 Day1,Day2 T1 T2 题解的相关文章

NOIp2016 Day1&amp;Day2 解题报告

Day1 T1 toy 本题考查你会不会编程. //toy //by Cydiater //2016.11.19 #include <iostream> #include <cstdlib> #include <cstdio> #include <cstring> #include <string> #include <algorithm> #include <queue> #include <map> #in

C# Tuple&lt;T1,T2....T&gt;元组的使用

1) 先说组元:一个数据结构,由通过逗号分割的,用于传递给一个程序或者操作系统的一系列值的组合. NET Framework 直接支持一至七元素的元组 Tuple<T1> Tuple<T1, T2> Tuple<T1, T2, T3> Tuple<T1, T2, T3, T4> Tuple<T1, T2, T3, T4, T5> Tuple<T1, T2, T3, T4, T5, T6> Tuple<T1, T2, T3, T4

&quot;SELECT * FROM T1,T2&quot; 与 join in 的关系

T1 于 T2  的表 如下: ------------------------------------------------------------------------------------------ SELECT * FROM T1,T2  (接过为2两表的笛卡儿积) 如下 ----------------------------------------------------------------------------------------------- SELECT *

Action&lt;T1, T2&gt;委托

封装包含两个参数的方法委托,没有返回值. 语法 public delegate void Action<in T1, in T2>( T1 arg1, T2 arg2 ) 类型参数 in T1:委托封装方法的第一个参数类型,此类型参数逆变. 用法 可以使用Action<T1, T2>委托以参数形式传递方法,而不用自定义委托.封装的方法必须与此委托的方法签名一致.也就是说,封装的方法也要有两个参数,没有返回值. 下面显式声明了一个名为ConcatStrings的委托.然后,它将两个方

020给定两个二叉树T1,T2判断T1是否是T2的子树(keep it up)

给定两个二叉树T1,T2判断T1是否是T2的子树 首先在T2中查找T1的根节点,如果找不到, 则T1不是T2的子树,如果能找到,我们可以 遍历T1,检查T1的每个结点是否对应的存在T2 中. 代码: struct TreeNode { int data; TreeNode* leftChild; TreeNode* rightChild; }; bool isExited(const TreeNode* vRoot1, const TreeNode *vRoot2, TreeNode* vRes

五一清北总结——day1+day2

五一清北学习总结 ——day1+day2 一.排序 1.快速排序 O(nlogn)的排序,用sort可以快速完成,但是可以被卡,手写sort利用随机数可以避免被卡掉. 代码: void quick_sort(int *a, int l, int r) { swap(a[l], a[rand()*rand() % (r -l + 1) +l]); int tmp = a[l]; int l_ = l, r_ = r; while (l < r) { while (l < r) { if (a[r

DateTime.Compare(t1,t2)比较两个日期大小

DateTime.Compare(t1,t2)比较两个日期大小,排前面的小,排在后面的大,比如:2011-2-1就小于2012-3-2返回值小于零:  t1 小于 t2. 返回值等于零 : t1 等于 t2. 返回值大于零:  t1 大于 t2. 如: [c-sharp] view plain copy using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace C

C#中的Expression&lt;Func&lt;T1,T2&gt;&gt; expression 怎么理解

有的时候在使用一些C#方法的时候会看到要求输入Expression<Func<T1,T2>> expression 这个参数 怎么去理解呢? Func是表示的委托类型,接受T1类型的参数,然后返回T2类型的值, 而Expression代表的是表达式类型 不妨这么去理解Expression<Func<int,double>> expression Expression是一个表达式,那么就写成lambda表达式 这么这个Lambda表达式接受int参数,然后返回

2019.2.21 T2题解

meet 大概思路就是 , 找出相交的路径 , 判断方向 , 分类讨论.. 假设已经找出了相交路径 ... 若方向相同 , 则找到相交路径上边权的最大值 , 若最大值>出发时间差 , 则可行. 原因: 由于方向相同所以在相交路径上这两个点的相对距离是不变的,看最大的边权即可. 这个东西用st表就可以搞定 若方向不同,就看看他们相遇的位置是不是相交路径上各个(不一定是最两头的点)边的端点之一. 因为题中说在边上才算 , 点不能算到边上. 所以不在点上就一定会在边上相遇. 然后就是怎么找相交路径了.