lqb赛前复习
lqf赛前复习(简略) ***打表过样例:回文日期:https://www.acwing.com/file_system/file/content/whole/index/content/355393/ ***暴力出奇迹:整数拼接:https://www.acwing.com/solution/content/22436/ ***dfs三道例题:ps:dfs记得剪枝(可行性,最优性(如最短路)) 1.递归实现指数型枚举:从 1~n 这 n 个整数中随机选取任意多个,输出所有可能的选择方案。 #include<bits/stdc++.h> using namespace std; const int N = 15; int n; int st[N];//保存状态 void dfs(int x){//枚举到第x位 if(x>n){ for(int i = 1; i <= n; i ++){ if(st[i]){ cout << i << ' '; } } cout << endl; return ; } st[x] = true; // 选x dfs(x + 1); //进入下一层 st[x] = false;//不选x,进入另一分叉 dfs(x + 1); } int main(){ cin >> n; dfs(1); return 0; } 2.递归实现排列型枚举 把 1~n 这 n 个整数排成一行后随机打乱顺序,输出所有可能的次序。 //next_permutation #include<bits/stdc++.h> using namespace std; int n; bool used[10]; int st[10]; int main(){ scanf("%d",&n); for(int i = 1; i <= n; i ++){ st[i] = i; } do{ for(int i = 1; i <= n; i ++){ printf("%d ",st[i]); } printf("\n"); }while(next_permutation(st+1,st+1+n)); return 0; } //dfs #include<bits/stdc++.h> using namespace std; const int N = 10; int n; int st[N];//0表示没放进数,1~n表示放进了哪些数 bool used[N];//表示这个数有没有用过 void dfs(int u){//当前枚举到第u位 if(u > n){//n个已经枚举完,当前搜到第n+1个,递归边界到达 for(int i = 1; i <= n; i ++){ printf("%d ", st[i]); } printf("\n"); return ; } //依次枚举树的每一条分支,当前位置能填什么数 for(int i = 1; i <= n; i ++){ if(!used[i]){ st[u] = i; used[i] = true; dfs(u + 1); //回溯 st[u] = 0; used[i] = false; } } } int main(){ scanf("%d",&n); dfs(1); return 0; } 3.递归实现组合型枚举 从 1~n 这 n 个整数中随机选出 m 个,输出所有可能的选择方案。 #include<bits/stdc++.h> using namespace std; int n,m; vector<int>arr; void dfs(int u){ //当这个数组存储的数的个数大于m时候就应该终止递归,马上返回,不能继续. //n - u + 1是剩下的数,例如第一位枚举完123到4的时候,就是5-4+1(剩4、5 要加一才能算上4本身) //arr.size()是已经存入的数,剩下的数加上存进去的数都小于m,那就必须得终止了 //递归进行的时候u是升序的,4 5 _,下一位进行递归枚举的时候 u = 6, 5 - 6 + 1 + 2 < 3. if(arr.size() > m || n - u + 1 + arr.size()< m){ return ; //可行性剪枝 } if(u == n+1){ // 这里为什么是u == n+1,此时是递归边界,由5个数字可以推出当枚举到6的时候,就应该输出前面所有的方案了. // 为什么能枚举到6?5不应该已经终止了吗?不!5的时候,是因为后续没有数,所以终止,而不是没有枚举到// 5,或者说没有到5即为终止. for(int i = 0; i < arr.size(); ++i){ printf("%d ",arr[i]); } printf("\n"); } arr.push_back(u); dfs(u + 1); arr.pop_back(); dfs(u + 1); } //以上代码由指数型枚举再加一个判断符合位数的条件得出.yxc大佬上课讲的另外一种思路在下面. int main(){ scanf("%d %d",&n,&m); dfs(1); return 0; } ***bfs:框架 void bfs(){ 将起始点放入队列; 标记访问; while(!q.empty()){ 访问队列中队首元素x 删除队首x for(x所有稍近的点){ if(该点未访问且合法){ push这个点 (接下来的操作步骤取决于题目要干嘛) } } } 队列为空搜索结束 } 1. -> next_permutation 全排列 五重循环以上 数字不能重复 要有顺序(用之前排序) 用法:do{ } while(next_permutation(&begin,&end)); 例题:三羊献瑞(百度可以搜到原题) #include<bits/stdc++.h> using namespace std; int main(){ int num[9] = {0,2,3,4,5,6,7,8,9}; int a,b,c; do{ if(num[0] != 0){ a = num[0] * 1000 + num[1] * 100 + num[2] * 10 + num[3]; b = 1000 + num[4] * 100 + num[5] * 10 + num[1]; c = 10000 + num[4] *1000 + num[2] *100 + num[1] * 10 + num[6]; if(a + b == c){ cout << b << endl; break; } } }while(next_permutation(num,num+9)); return 0; } 2. 闰年:能被4整除不能被100整除,且能被400整除 scanf函数的使用:http://c.biancheng.net/view/160.html 闰年常用函数: int mon_day[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31} ; bool isyear(int year){ if(year % 400 == 0 || year % 4 ==0 && year %100!=0){ return true; return false; } } 1 3 5 7 8 ->31号 2月->28 29(闰) 3. 二分: 二分模板一共有两个,分别适用于不同情况。 算法思路:假设目标值在闭区间[l, r]中, 每次将区间长度缩小一半,当l = r时,我们就找到了目标值。 版本1 当我们将区间[l, r]划分成[l, mid]和[mid + 1, r]时,其更新操作是r = mid或者l = mid + 1;,计算mid时不需要加1。 C++ 代码模板: int bsearch_1(int l, int r) { while (l < r) { int mid = l + r >> 1; if (check(mid)) r = mid; else l = mid + 1; } return l; } 版本2 当我们将区间[l, r]划分成[l, mid - 1]和[mid, r]时,其更新操作是r = mid - 1或者l = mid;,此时为了防止死循环,计算mid时需要加1。 C++ 代码模板: int bsearch_2(int l, int r) { while (l < r) { int mid = l + r + 1 >> 1; if (check(mid)) l = mid; else r = mid - 1; } return l; } 125 评论 shibaguji ? 1个月前 ??? 回复 无意间發现了这个网站真是如入宝山呀,在这裡也分享一点心得,希望能对大家有点帮助。 虽然这模版真的是非常好用,但是每次在决定那个 check 函数时总是让我想破头, 因为一不小心写反就找不到了,一路跌跌撞撞后稍稍有点心得,如果有错还请各位高手指正 假设有一个总区间,经由我们的 check 函数判断后,可分成两部分, 这边以o作 true,.....作 false 示意较好识别 如果我们的目标是下面这个v,那麽就必须使用模板 1 ................vooooooooo 假设经由 check 划分后,整个区间的属性与目标v如下,则我们必须使用模板 2 oooooooov................... 所以下次可以观察 check 属性再与模板1 or 2 互相搭配就不会写错啦 MiraMo ? 25天前 ??? 回复 感觉同学说的很有道理~ 我的想法和同学也类似,模板1就是在满足chek()的区间内找到左边界,模板2在满足check()的区间内找到右边界。然后无论是左边界还是右边界,都应该是整个区间中某一段满足某性质(如单调不降)与另一段不满足该性质的分界点(也就是同学的v)。如有错误,请高手指正~ yxc ? 5天前 ??? 回复 总结得不错hh yxc ? 5天前 ???回复了 MiraMo 的评论 ??? 回复 没有问题hh 嗯丶麦格芬 ? 1天前 ??? 回复 就是像数的范围那道题啊,x的起始位置。 作者:yxc 链接:https://www.acwing.com/blog/content/31/ 来源:AcWing 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 4.前缀和(。。高中数列知识) 处理静态数组(不能动态随机存取) S[i] = a[i] + S[i - 1] a5+...an=Sn-S4 aL+...+an=Sn-SL 与前缀和相关的->子矩阵的和(将二维数组压缩为一维数组) 输入一个n行m列的整数矩阵,再输入q个询问,每个询问包含四个整数x1, y1, x2, y2,表示一个子矩阵的左上角坐标和右下角坐标。 对于每个询问输出子矩阵中所有数的和。 输入格式 第一行包含三个整数n,m,q。 接下来n行,每行包含m个整数,表示整数矩阵。 接下来q行,每行包含四个整数x1, y1, x2, y2,表示一组询问。 输出格式 共q行,每行输出一个询问的结果。 数据范围 1≤n,m≤1000, 1≤q≤200000, 1≤x1≤x2≤n, 1≤y1≤y2≤m, ?1000≤矩阵内元素的值≤1000 输入样例: 3 4 3 1 7 2 4 3 6 2 8 2 1 2 3 1 1 2 2 2 1 3 4 1 3 3 4 输出样例: 17 27 21 思路:一维数组->一维前缀和数组 二维数组->二维前缀和数组 集合思想(A+B=A并B-A交B) 注意,下面的xy是与一般的坐标系相反的,y轴是x,x轴是y 为什么?大概是习惯问题吧- - 或者是因为二维数组里面一个下标对应多个值. (x,y)为坐标。 Sx,y表示从1,1到x,y这个点的子矩阵的和。 Sxy=Sx-1,y + Sx,y-1 - Sx-1,y-1 + ax,y; 那么得到二维前缀和矩阵后,如何在二维前缀和数组里计算任意一个子矩阵的和? 画图可以很容易看出来怎么计算,就不细说了。 例如计算x1,y1 -> x2,y2的和 利用二维前缀和数组 和(x1,y1 -> x2,y2) = S(x2,y2)-S(x2,y1-1)-S(x1-1,y2)+S(x1-1,y1-1) #include<bits/stdc++.h> using namespace std; const int N = 1010; int n,m,q; int x1,y1,x2,y2; int a[N][N]; int s[N][N]; int main(){ scanf("%d %d %d",&n,&m,&q); for(int i = 1; i <= n; i ++){ for(int j = 1; j <= m; j ++){ scanf("%d",&a[i][j]); s[i][j] = s[i - 1][j] + s[i][j - 1] + a[i][j] - s[i - 1][j - 1]; } } while(q --){ scanf("%d %d %d %d",&x1,&y1,&x2,&y2); int res = s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]; printf("%d\n",res); } return 0; } 5. //注意C++的取余是正数余正数 负数余负数 不是真正的取余(只有0和正整数) //所以要在取余的式子上面做些等价变形 //比如(a - b)%p = (a%p - b%p + p)%p 取余运算 (a + b) % p = (a % p + b % p) % p (1) (a - b) % p = (a % p - b % p + p) % p (2) (a * b) % p = (a % p * b % p) % p (3) (a^b) % p = ((a % p)^b) % p (4) 结合律: ((a+b) % p + c) % p = (a + (b+c) % p) % p (5) ((a*b) % p * c)% p = (a *(b*c)%p) % p (6) 交换律: (a + b) % p = (b+a) % p (7) (a * b) % p = (b * a) % p (8) 分配律: ((a +b)% p * c) % p = ((a * c) % p + (b * c) % p) % p (9) 重要定理: 若a≡b (% p),则对于任意的c,都有(a + c) ≡ (b + c) (%p);(10) 若a≡b (% p),则对于任意的正整数c,都有(a * c) ≡ (b * c) (%p);(11) 若a≡b (% p),c≡d (% p),则 (a + c) ≡ (b + d) (%p),(a - c) ≡ (b - d) (%p), (a * c) ≡ (b * d) (%p); (12) 6.数据范围反推算法: 一般ACM或者笔试题的时间限制是1秒或2秒。 在这种情况下,C++代码中的操作次数控制在 107107 为最佳。 作者:yxc 链接:https://www.acwing.com/blog/content/32/ 来源:AcWing 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 7.数学知识: C/C++中只有 "以e为底,省略e"log() 和 log10(),通过换底公式logab = logb/loga 两整数拼接: #include<bits/stdc++.h> using namespace std; typedef long long LL; LL a[2]; LL n; int main(){ cin >> a[1]; cin >> a[2]; int s = log10(a[1]); int s1 = log10(a[2]); LL res = a[1] * pow(10,s1+1) + a[2]; cout << res << endl; LL res1 = a[2] * pow(10,s + 1) + a[1]; cout << res1 << endl; return 0; } 买不到的数目结论:两个数不能组合的最大的数为两个数之积减去两个数的和 十进制转换其他进制: count = 0; do{ yushu[count ++] = number % jinzhi; number /= jinzhi; }while(number != 0); for(i=count - 1; i >= 0; i ++){ printf("%d",yushu[i]); } 其他进制转十进制 y = 0; product = 1; x(p) -> x(10) while(x != 0){ y = y + (x % 10) * product; x = x / 10; product = product * p; } int gcd(int a,int b){ if (b == 0) return a; return gcd(b, a % b); }
/*简洁写法:
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
*/
int lcm(int gcd,int a,int b){ return a / gcd * b; } 卡特兰数 1 1 5 14 ...(C(2n,n))/(n+1) = C2n,n - C2n,n-1 (常见题目:出栈次序(国赛:火车进站)) 组合数:Cnm = Cn-1,m + Cn-1, m-1; typedef long long LL(ps: 蓝桥杯编译环境%lld) { if(m == 0 || m == n) return 1; return C(n-1, m) + C(n-1, m-1); } 筛法求素数:O(n)
int primes[N], cnt;
bool st[N];
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ ){
if (!st[i]) primes[cnt ++ ] = i;
for (int j = 0; primes[j] <= n / i; j ++ )
{
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
素数打表: 8.dp -> 一般是求最值or个数 背包问题抽象化:从若干个东西中炸一些东西出来进行组合,在某种条件下 也就是合法的情况下,所有组合中的最大值是什么. dp划分:1.状态表示:1.集合:感性认识 2.属性:max;min;数量 (注:抽象–>集合内存储的是什么值) 2.状态计算 例子:01背包 f[i,j] 状态表示 1.集合:所有只从前i个物品中选,且总体积不超过j的所有方案的集合 2.属性: 找最大值 状态计算(将全集划分为不同的子集,画韦恩图) 划分依据:找最后一个不同点(例如01背包问题:选or不选) 01背包全集就分为:1.所有不选择第i个物品的方案 and 2.所有选择第i个物品的方案 1.因为不选第i个物品,所以状态计算是f[i - 1,j]; 2.含义:从前i个物品中选,一定选第i个物品,且总体积不超过j 该方案列举: ..........i ..........i ..........i ..........i .... 我们要从上面的方案中找到有最大值的方案,然后的话第i个是必选的, 所以影响最大值的仅是左边的......,可以划分为左右(只含i)两部分, 就是说整个方案的最大值=左边的最大值+"i"的最大值 故我们可以同时将所有方案减去i,找到左边的最大值,然后把最大值加上i的价值 计算公式为f[i - 1, j - v[i]] + w[i] 完全背包: f[i][j] 集合划分:所有只从前i个物品中选,且总体积不超过j的集合 集合属性:max 状态计算: 选择 0 / 1 / 2 / 3 / 4 / ..../ k /...个物品 0的子集:从(1,i) 中选物品,不选第i个物品,且总体积小于等于j. ->f[i - 1][j] k的子集:....,k个i ....,k个i ....,k个i ....,k个i ->分为两部分: 不含i,k个i f[i - 1][j - k*v[i]] + kw[i] ->f[i][j] = max(f[i - 1][j],f[i - 1][j - vi] + w[i]....,f[i - 1][j - k*v[i]] + kw[i]); 又由于f[i][j - vi] = max(f[i - 1],[j - v],f[i - 1][j - 2v]+w[i]...,f[i - 1][j - k*v[i]] + (k - 1)w[i]...); 可以看见右边那部分上下两个括号是差了个w[i],又第二堆东西最大值就是f[i][j - v],所以可以优化成下面: 所以f[i][j] = max(f[i - 1][j],f[i][j - v[i]] + w[i]); LCS.(最长上升子序列)f[i] 集合:所有以a[i]结尾的严格单调上升子序列 属性:max 状态计算: (最后不同的一步:a[i],a[i]难划分就再倒数一步,a[i - 1]) (下标从1开始) a[1] a[2] a[3]...a[i-1]、空(只包含a[i],没有倒数第二个) 归纳:第k类中的上升子序列: .......a[k] a[i] -------a[k] a[i] ;‘;’a[k] a[i] .... 分左右两半->f[k] + 1; 故f[i] = max(f[i],f[k] + 1); 9.快速幂:a^b % m typedef long long LL; LL binaryPow(LL a,LL b,LL m){ if(b == 0) return 1; if(b % 2 == 1) return a * binaryPow(a, b - 1, m) % m; else{ LL tmp = binaryPow(a,b / 2, m); return mul * mul % m; } } 10. 树: 根节点为第一层 结点的子树数目称为节点的度 树的度:最大的度 深度:根节点(深度为1)自顶向下累加至该节点的值 高度:从叶子节点(高度为1)自底向上累加到该节点的高度 树的深度指最大深度 满二叉树:每一层的节点个数都达到了该层能达到的最大节点数 二叉树性质:第i层节点数最多为2^i 深度为h的二叉树最多有2^h - 1个节点 任何一个二叉树:叶结点为n个,度为2的节点个数为n2,则有n = n2 + 1