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); (126.数据范围反推算法:
一般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

 

posted @ 2020-10-17 00:00  WriteOnce_layForever  阅读(364)  评论(1编辑  收藏  举报