[关闭]
@fcxxzux 2017-01-28T00:44:13.000000Z 字数 5247 阅读 1125

寒假菜鸟先飞系列赛题目选讲

第1场 Prob 07

s[a] + s[b] + s[c] == s[d]
看到这个东西,一般尝试采用套路:meet in the middle(中间相遇法)
具体来说,枚举所有s[a]+s[b],统计每种可能的结果有几个组合方式
然后枚举s[d]-s[c](把s[c]移项即可得到),去前面s[a]+s[b]的统计结果中找。

这样,你暴力的枚举O(),变成了O(+),这个x取决于查找方式

注意到,那么
20万啊,好小啊,我们开个int数组 int count[200001],直接计数,然后再枚举s[d]-s[c],去找,就行了。

这样,这个查找方式是O(1)的,于是总体时间复杂度就是O()

  1. int arr[1005];
  2. int cnt[200005];
  3. int main(){
  4. int T,n;
  5. for(scanf("%d",&T);T--;){
  6. scanf("%d",&n);
  7. memset(cnt,0,sizeof(cnt));
  8. for(int i=0;i<n;i++)scanf("%d",&arr[i]);
  9. for(int i=0;i<n;i++){
  10. for(int j=0;j<n;j++){
  11. cnt[arr[i]+arr[j]]++;
  12. }
  13. }
  14. ll ans=0;
  15. for(int i=0;i<n;i++){
  16. for(int j=0;j<n;j++){
  17. if(arr[i]-arr[j]>=0)
  18. ans+=cnt[arr[i]-arr[j]];
  19. }
  20. }
  21. out(ans);puts("");
  22. }
  23. return 0;
  24. }

第2场 Prob 03

我没试过暴力,但是记得hdoj上有个题,类似的,找这种可以循环移位的串的最小表示(给cab,输出abc,反正移位方法和这题一样的),我是瞎剪枝的暴力搞过去的。

反正你们这里很多人说暴力过了,我也不管了。
但是正解一定要说一句:
这种 循环字符串的最小表示法的问题 ,是有优秀的算法的,时间复杂度O(n)。
说实话,我也不会推导,但是加入你的模板是没错的。

具体做法,请参考博客:
http://blog.csdn.net/cclsoft/article/details/5467743
反正,用上述做法,找出2个字符串的最小表示,然后比较大小,输出比较结果,完事。

第2场 Prob 04

推荐前置知识:广度优先搜索(BFS)、拓扑排序

然后其实我的idea的确脱胎于拓扑排序:
在类bfs实现拓扑排序的时候,我们当一个点未访问的前置点个数为0(或者说,删点和相应的边以后,某个点在新图中的入度为0)的时候,加入拓扑序列,把这个点删去。

那么这里也是类似的道理:
我们开一个数组,统计每个人有多少种球的数量<=Watano 手中相应种类的球的个数。当种类数==K的时候,这个人可以离场,把所有的球都给Watano 了。

那么现在的主要矛盾点是,怎么快速的知道,一个人的一种球的数量,< Watano手上的相应的求的数量呢?

不妨采取以下方法:
对每种球,记录球的数量,和相应的人的id,然后按球的数量从小到大排序。
同时每种球,维护一个“指针”,记录之前已经知道有多少个人手上的,这种球的数量小于Watano所有的了。

那么,一开始,我们就推进所有种类求的“指针”,相应的更新那个人有多少种球的数量<=Watano 手中相应种类的球的个数,这样有一些人就要给出他手上的所有球了。这些人加入队列,然后每个人给出球,继续往后推指针,直到队列里没人为止。

时间复杂度:上面有个排序,,然后对每人每种球,只会扫过一次,,所以整体时间复杂度

所以啊,代码有点长……

  1. #include <stdio.h>
  2. #include <ctype.h>
  3. #include <string.h>
  4. #include <stdlib.h>
  5. #include <limits.h>
  6. #include <math.h>
  7. #include <algorithm>
  8. #include <queue>
  9. using namespace std;
  10. typedef long long ll;
  11. template <class T>
  12. inline void scan(T &ret) {
  13. char c; ret=0;
  14. while((c=getchar())<'0'||c>'9');
  15. while(c>='0'&&c<='9') ret=ret*10+(c-'0'),c=getchar();
  16. }
  17. int n,K;
  18. int d[10005][11];
  19. int cnt[10005];
  20. struct Pair{
  21. Pair(){}
  22. Pair(int a,int b):val(a),idx(b){}
  23. int val,idx;
  24. bool operator<(const Pair&b)const{
  25. return val<b.val;
  26. }
  27. }p[11][10005];
  28. int pt[11];
  29. int main(){
  30. while(~scanf("%d%d",&n,&K)){
  31. for(int i=0;i<K;i++)scan(d[0][i]);
  32. for(int i=1;i<=n;i++){
  33. cnt[i]=0;
  34. for(int j=0;j<K;j++){
  35. scan(d[i][j]);
  36. p[j][i-1]=Pair(d[i][j],i);
  37. }
  38. }
  39. for(int j=0;j<K;j++)sort(p[j],p[j]+n);
  40. queue<int> q;
  41. memset(pt,0,sizeof(pt));
  42. for(int j=0;j<K;j++){
  43. while(pt[j]<n&&p[j][pt[j]].val<d[0][j]){
  44. int y=p[j][pt[j]].idx;
  45. cnt[y]++;
  46. if(cnt[y]==K)q.push(y);
  47. pt[j]++;
  48. }
  49. }
  50. while(!q.empty()){
  51. int x=q.front();q.pop();
  52. for(int j=0;j<K;j++)d[0][j]+=d[x][j];
  53. for(int j=0;j<K;j++){
  54. while(pt[j]<n&&p[j][pt[j]].val<d[0][j]){
  55. int y=p[j][pt[j]].idx;
  56. cnt[y]++;
  57. if(cnt[y]==K)q.push(y);
  58. pt[j]++;
  59. }
  60. }
  61. }
  62. for(int j=0;j<K;j++){
  63. printf(j==K-1?"%d\n":"%d ",d[0][j]);
  64. }
  65. }
  66. return 0;
  67. }

第2场 Prob 06

一个个比对过去太慢了……
我们不妨搞成一个很方便比较的形式,比如,数串,甚至就一个数?
比如
*2*
111
000
记成2111000,

大矩阵中的
1234
5006
7089
我们有2个小矩阵:
123
500
708

234
006
089
我们分别记作:2500708 和 3006089

这样,我们对查询、对小矩阵都有统一的表示方法了。
然后呢?各显神通的时候到了。

比如我的干法:
把查询按值排序,然后枚举小矩阵,在查询中二分查找,相等的都答案+1,最后查询恢复输入顺序,输出。
(下面的实现的时间复杂度不是很对,相等的都加1应该也是结合upper_bound 和 lower_bound,确定上下界,然后用前面+1,后面-1,最后前缀和确定结果的方法,这样时间复杂度才是真O(nm log q了))

你还可以,把小矩阵排序,然后对每个查询,二分查找相等的数量(upper_bound 和 lower_bound的结合使用),就是答案。

(题目输入似乎有问题,小矩阵好像不保证左上角和右上角不为数字,你就硬生生当做星号吧……)

  1. #include <stdio.h>
  2. #include <ctype.h>
  3. #include <string.h>
  4. #include <stdlib.h>
  5. #include <limits.h>
  6. #include <math.h>
  7. #include <algorithm>
  8. using namespace std;
  9. typedef long long ll;
  10. char str[4][4];
  11. struct Query{
  12. Query(){}
  13. Query(int a):val(a){}
  14. int idx,val,cnt;
  15. void get(){
  16. val=cnt=0;
  17. for(int i=0;i<3;i++){
  18. scanf("%s",str[i]);
  19. }
  20. val=str[0][1]-'0';
  21. for(int i=1;i<3;i++){
  22. for(int j=0;j<3;j++){
  23. val=val*10+(str[i][j]-'0');
  24. }
  25. }
  26. }
  27. bool operator<(const Query&b)const{
  28. return val<b.val;
  29. }
  30. }q[1005];
  31. bool cmp(const Query&a,const Query&b){
  32. return a.idx<b.idx;
  33. }
  34. int qcnt;
  35. int T,n,m,Case=1;
  36. char mp[1005][1005];
  37. int main(){
  38. for(scanf("%d",&T);Case<=T;Case++){
  39. scanf("%d%d",&n,&m);
  40. for(int i=0;i<n;i++)scanf("%s",mp[i]);
  41. scanf("%d",&qcnt);
  42. for(int i=0;i<qcnt;i++){
  43. q[i].get();q[i].idx=i;
  44. }
  45. sort(q,q+qcnt);
  46. for(int i=0;i<n-2;i++){
  47. for(int j=0;j<m-2;j++){
  48. int val=mp[i][j+1]-'0';
  49. for(int k=1;k<3;k++){
  50. for(int l=0;l<3;l++){
  51. val=val*10+(mp[i+k][j+l]-'0');
  52. }
  53. }
  54. int idx=lower_bound(q,q+qcnt,Query(val))-q;
  55. while(idx<qcnt && q[idx].val==val){
  56. q[idx].cnt++;
  57. ++idx;
  58. }
  59. }
  60. }
  61. sort(q,q+qcnt,cmp);
  62. printf("Case %d:\n",Case);
  63. for(int i=0;i<qcnt;i++)printf("%d\n",q[i].cnt);
  64. }
  65. return 0;
  66. }

第2场 Prob 07

一维的版本做过吧?
一个直线上n个点,坐标各异,现在请找一个位置,使得n个点移动到那个位置的距离和最小。
(呃,是小学奥数题吗?忘了……)
反正正解就是中位数,偏离(奇数个情况下的)中位数/(偶数个情况下的)中间两个数那一段之外,都会导致距离和变大。

现在,这里要求,定n个x位置。
那么,我们先每条横线上的点从左到右排序,然后每行的第1、2、3、...个点取中位数,作为新的位置即可。

只有一列就不多说了,和一维版本一样。
多列的时候,要说明:定的n个位置各不相同。
然而,输入保证没有2个点在同一个位置上,也就是说,第二列的点的x坐标只会至少整体往后移动一个单位,也就是,中位数至少+1,那么就不会选出一样的坐标了(只要你在偶数个点的情况下,选择中位数的策略不变的话。)

(不是郑钧尹说我都没注意到,我的代码里忘了先每行的点排个序了,然后居然过了,醉了。)

  1. int T,y,n;
  2. int x[1005][1005];
  3. int main(){
  4. for(scanf("%d",&T);T--;){
  5. scanf("%d%d",&y,&n);
  6. for(int j=0;j<y;j++){
  7. for(int i=0;i<n;i++){
  8. scan(x[i][j]);
  9. }
  10. }
  11. ll ans=0;
  12. for(int i=0;i<n;i++){
  13. sort(x[i],x[i]+y);
  14. int mark=x[i][y/2];
  15. for(int j=0;j<y;j++)ans+=abs(x[i][j]-mark);
  16. }
  17. out(ans);puts("");
  18. }
  19. return 0;
  20. }

第2场 Prob 09

先纸上看看。
考虑枚举 N=xxxx,这后面一串,由多少个数组成。
比如N=4
后面1个数4=4
后面4个数4=1+1+1+1
很简单。
后面2个数,每个数不为0呢?
4=1+3 4=2+2 4=3+1
后面3个数,每个数不为0呢?
4=2+1+1 4=1+2+1 4=1+1+2

……………………^_^
等等,有没有觉得很熟悉?
高中做过吗?x个相同的球,放入y个相同的盒子,每个盒子不能为空的题?
这不就是高中的排列组合的经典题吗?还记得吗?
那么,对于N=4的情况,方案数,用组合表示,为:

我们再复习一下高中的知识:
,结果是多少啊?
记住了:

(通俗大白话:杨辉三角的某一整行的和,是2的幂次
具体来说,开头是的,这一整行的和为

然后我就不多说了。
除了写代码的时候。
表示2的x次方,位运算可以写成1<<n,但是对于long long的范围的,1<<(long long)n是不对的,要写成1LL<<n

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注