Part1:模拟题
今天的题有点难啊,只得了第一题的60分。不过让人欣慰的是,大多数人都只得了第一题的六十分。
60分做法:z老师说的是打表或者暴力就能拿到。暴力应该是要做一点优化。我是枚举了a和(a*b)然后判断b是否是因数(即在数组a中)。刚开始我想用set储存因数,因为可以直接用s.count()来判断是不是因数。但是有别的问题。就是他只能访问到第一个或最后一个数。所以要更新a的话只能删掉第一个数。这样的话后面再判断是否为因数时会受影响。比较气的是我都写完了才发现这个问题,然后又删掉老老实实的用数组写了一遍,耽误了不少时间。(不过后两题也不会写)
我的代码:
1 #include <iostream> 2 #include <cmath> 3 #include <cstring> 4 #include <cstdio> 5 #include <cstdlib> 6 #include <algorithm> 7 using namespace std; 8 int n,ans,ge; 9 int a[10101]; 10 bool is_prime(int x) //判断质数 11 { 12 if(x==2) return true; 13 for(int i=2;i<=sqrt(x);i++) 14 if(x%i==0) return false; 15 return true; 16 } 17 void find(int x) //找因数 18 { 19 for(int i=1;i<=x;i++) 20 if(x%i==0) a[++ge]=i; 21 } 22 23 int main() 24 { 25 26 freopen("a.in","r",stdin); 27 freopen("a.out","w",stdout); 28 29 scanf("%d",&n); 30 ans=1; 31 for(int i=2;i<=n;i++) 32 { 33 if(is_prime(i)) {ans+=3;continue;} //即(1,1) (1,i) (i,1)三种情况 34 memset(a,0,sizeof(a)); 35 ge=0;find(i); 36 ans+=2*ge-1; //即(1,1) (1,a[1])(a[1],1) (1,a[2])(a[2],1) ... (1,a[ge])(a[ge],1) 37 for(int j=2;j<ge;j++)//枚举a 38 { 39 for(int k=j+1;k<=ge;k++) //枚举(a*b) 40 { 41 if(a[k]%a[j]!=0) continue; /*除不尽的就不要再考虑了。这点刚开始没想到,错了好几次*/ 42 int c=a[k]/a[j]; 43 if(i%c==0) ans++;//如果b也在a数组中,则符合条件 44 } 45 } 46 } 47 printf("%d",ans); 48 return 0; 49 }
100分做法:题意实际上就是枚举a,b找有多少数是(a*b)的倍数(将倍数设为c),即枚举a*b*c。可假定a<b<c,那么将枚举出来的数*6即为最终答案【(1,2,3)(1,3,2)(2,1,3)...共六种排列方式,答案一样】(此处埋下一个BUG)
因为a<b<c,所以a的循环只需要到,即(a=1;a*a*a<=n;a++)就行了。
同理,b*b<=n/a(a*b*c<n b*c<n/a;因为b<c,所以b*b<n/a)
然而仅仅这样做是不对的。因为我们没有考虑其中有2个或3个数相等的情况。下面要再开个循环考虑一下。
标程:
1 #include<cstdio> 2 #include<cstdlib> 3 #include<cstring> 4 5 using namespace std; 6 7 long long n; 8 9 #ifdef unix 10 #define LL "%lld" 11 #else 12 #define LL "%I64d" 13 #endif 14 15 int main() 16 { 17 freopen("a.in","r",stdin); 18 freopen("a.out","w",stdout); 19 20 scanf(LL,&n); 21 long long ans=0,tmp=0; 22 for (long long a=1,v;a*a<=(v=n/a);a++,ans++) 23 for (long long b=a+1;b*b<=v;b++) 24 tmp+=n/(a*b)-b; 25 ans+=tmp*6; 26 tmp=0; 27 for (long long a=1,v;(v=a*a)<=n;a++) //考虑两个/三个数相等的情况 28 { 29 tmp+=n/v; 30 if (a*a<=n/a) tmp--; 31 } 32 ans+=tmp*3; 33 printf(LL "\n",ans); 34 35 return 0; 36 }
今天的题解笔记就到这里,后两题太难了弃了。
其他的一点小tips:
1 //longlong在不同系统的输出不同,以下写法可以避免在不知道测评系统的情况下导致输出错误 2 #ifdef WIN32 3 #define LL "%I64d" 4 #else 5 #define LL "%lld" 6 #endif 7 //------------------------------------------------------------------------------------------------------- 8 //在实数上做二分不能以L+1==R作为退出条件,不然会无限循环下去。可以直接枚举,达到一定的精度(比如30次或者50次)
Part2:数据结构专题
- 常见的数据结构:
- 数组、链表
- 队列、栈
- 堆(仅需知道如何用priority_queue实现)
- 并查集
- 树状数组或线段树(掌握一个即可,线段树学习资料:线段树完整版)
- 树
- 树上LCA(最近公共祖先,用倍增)
- DFS序列:把一棵二维的树通过DFS搜索顺序记录为线性序列,且每棵子树的所有节点都在这个序列上的一段连续区间上:
左图的DFS序列为:1 2 4 5 3 6 7 转换为线性后,就可以用线段树解决相关问题了:)
9.括号序列:依然基于DFS 【名称来源:+为左括号,-为右括号,则和为零是是合法的括号】
还是上面那个图,它的括号序列为:+1 +2 +4 -4 +5 -5 -2 +3 +6 -6 +7 -7 +8 -8 -3 -1
-4代表4已经遍历完了
那么,它有什么用呢?
举个例子,若要找1——3——6这条链,我们在上面的这个序列中找到+1,+6,发现不属于这条链上的点全都抵消掉了。这样我们就能找到所有这条链经过的点了。
注意:括号序列有一个先决条件。那就是序列必须是自上而下的,不会转弯(比如5——2——3——1就不行,在1处转了弯)
10.启发式合并:每次把最小的并进去,这样最快是O(nlogn)的
11.分治:归并排序
12.单调队列:维护单调性。
上边这个序列显然是不单调的。
为了让它变成单调序列我们进行如下操作:若当前元素插入后会导致序列不单调,则删掉原序列的最后一个数,可重复多次,直到插入当前元素后仍为单调序列。
它的应用主要就是在滑动窗口。下面通过一道例题来体会思想: