ACM常用模板(+模板题)(基础)

目录

  • 大数
  • 二分
  • 枚举排列
  • 子集生成
  • n皇后回溯
  • 并查集
  • 树状数组
  • KMP,Sunday,BM
  • 01背包,完全背包
  • 最长(不)上升或下降子序列
  • 最长公共子序列
  • 拓扑排序
  • 欧拉路径和回路
  • 搜索
  • 最小生成树
  • 最短路
  • GCD和LCM
  • 埃拉托斯特尼筛法
  • 唯一分定理
  • 扩展欧几里得
  • 欧拉函数
  • 快速幂
  • 矩阵快速幂

说明

  • 虽然只打了不到一年的ACM,但是在ACM中一些算法以后还是可能用到的,在这里进行一个小小的总结,总结了一些简单常见的算法模板(比较适合新手)
  • 模板终究只是模板,最好还是自己真正掌握,经常依赖模板,只是自己还没掌握的表现;
  • 本人只是一个ACM的小白,不是很全,代码也写的坑坑洼洼。。。,大佬勿喷,可能以后学习了新的算法再补过来,主要还是方便以后自己复习。

大数

加法,乘法模板

//题目链接 : http://poj.org/problem?id=2506
//题目大意 : 就是问你用2*1,1*2,2*2的砖拼成2*n的长方形,有多少种拼法
//解题思路 : 考虑n的时候,假设我们已经铺好了n-1块砖,第n块只能竖着放
            //假设我们已经铺好了n-2块砖,最后两列有3种方式,但是其中有一种方法和上面是相同的
            //所以f[n] = 2* f[n-2] + f[n-1] 
#include 
#include 
#include 
using namespace std;
const int maxn = 10000 + 10;

//加法
string bigIntegerAdd(string s1,string s2){
    int a[maxn],b[maxn];
    memset(a,0,sizeof(a));
    memset(b,0,sizeof(b));
    int len1 = s1.size(),len2 = s2.size();
    int maxL = max(len1,len2);
    for(int i = 0; i < len1; i++)a[i] = s1[len1-1-i]-'0';
    for(int i = 0; i < len2; i++)b[i] = s2[len2-1-i]-'0';
    for(int i = 0; i < maxL; i++){
        if(a[i]+b[i] >= 10){
            int temp = a[i]+b[i];
            a[i] = temp%10;
            a[i+1] += (temp/10);
        }
        else a[i] += b[i];
    }
    string c = "";
    if(a[maxL] != 0)
        c += a[maxL] + '0';
    for(int i = maxL-1; i >= 0; i--)c += a[i] + '0';
    return c;
}

//乘法
string bigIntegerMul(string s1,string s2){
    int a[maxn],b[maxn],c[maxn*2 + 5];
    memset(a,0,sizeof(a));
    memset(b,0,sizeof(b));
    memset(c,0,sizeof(c));
    int len1 = s1.size(),len2 = s2.size();
    for(int i = 0; i < len1; i++)a[i] = s1[len1-1-i]-'0'; //倒置
    for(int i = 0; i < len2; i++)b[i] = s2[len2-1-i]-'0';
    for(int i = 0; i < len1; i++){
        for(int j = 0; j < len2; j++){
            c[i+j] += a[i]*b[j];
        }
    }
    for(int i = 0; i < maxn*2; i++){
        if(c[i] >= 10){
            c[i+1] += c[i]/10;
            c[i] %= 10;
        }
    }
    string ans = "";
    int i;
    for(i = maxn * 2; i >= 0; i--)
        if(c[i] != 0)
            break;
    for(;i >= 0; i--)ans += c[i] + '0';
    return ans;
}

int main(){
    //freopen("in.txt","r",stdin);
    int n;
    string s[255];
    s[0] = "1",s[1] = "1";  //注意0的时候是1
    for(int i = 2;i <= 255; i++){
        string temp = bigIntegerMul("2",s[i-2]);
        s[i] = bigIntegerAdd(s[i-1],temp);
    }
    while(~scanf("%d",&n))
        cout<<s[n]<<endl;
    return 0;
}

减法模板

#include <bits/stdc++.h>
const int maxn = 200 + 10;
using namespace std;
typedef long long LL;

//具体实现
string subInfo(char *s1,char *s2){
    int a[maxn],b[maxn];
    memset(a,0,sizeof(a));
    memset(b,0,sizeof(b));
    int len1 = strlen(s1),len2 = strlen(s2);
    int maxLen = max(len1,len2);
    for(int i = 0; i < len1; i++) a[i] = s1[len1 - i - 1] - '0';
    for(int i = 0; i < len2; i++) b[i] = s2[len2 - i - 1] - '0';
    for(int i = 0; i < maxLen; i++){
        if(a[i]-b[i] < 0){
            a[i] = a[i]+10-b[i];
            a[i+1] -= 1;
        }
        else a[i] -= b[i];
    }
    string str = "";
    int i;
    for(i = maxLen-1; i >= 0; i--)if(a[i] != 0)break;
    for(;i >= 0; i--)str += a[i]+'0';
    return str;
}

//大数减法的模板
string bigIntegerSub(char *s1,char *s2){
    if(s1 == s2)
        return "0"; //相等
    int len1 = strlen(s1),len2 = strlen(s2);
    if(len1 > len2)
        return subInfo(s1,s2);
    else if(len1 < len2)
        return "-" + subInfo(s2,s1); //负数
    else {                        //长度相等时判断大小
        for(int i = 0; i < len1; i++){
            if(s1[i]-'0' > s2[i]-'0')
                return subInfo(s1,s2);
            else if(s1[i]-'0' < s2[i]-'0')
                return "-" + subInfo(s2,s1);
        }
    }
}

int main(){
    char s1[maxn],s2[maxn];
    scanf("%s\n%s",s1,s2);
    cout<<bigIntegerSub(s1,s2)<<endl;
    return 0;
}

求阶乘以及位数模板

//http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=28
//大数阶乘的模板
#include 
using namespace std;
const int maxn = 100000 + 10;

//大数计算阶乘位数
//lg(N!)=[lg(N*(N-1)*(N-2)*......*3*2*1)]+1 = [lgN+lg(N-1)+lg(N-2)+......+lg3+lg2+lg1]+1;
int factorialDigit(int n){
    double sum = 0;
    for(int i = 1; i <= n; i++){
        sum += log10(i);
    }
    return (int)sum+1;
}

//大数计算阶乘
string bigFactorial(int n){
    int ans[maxn],digit = 1;
    ans[0] = 1;
    for(int i = 2; i <= n; i++){
        int num = 0;
        for(int j = 0; j < digit; j++){
            int temp = ans[j]*i + num;
            ans[j] = temp%10;
            num = temp/10;
        }
        while(num != 0){
            ans[digit] = num%10;
            num /= 10;
            digit++;
        }
    }
    string str = "";
    for(int i = digit-1; i >= 0; i--)
        str += ans[i] + '0';
    return str;
}

int main(){
    int n;
    while(~scanf("%d",&n)){
        //cout<
        cout<<bigFactorial(n)<<endl;  //求出阶乘
    }
    return 0;
}

二分

二分的写法可以有很多种,这里列举几个常见的,主要是上界确定,区间以及查找元素是否重复的问题,也可以看看这篇文章。

  • 求最小的i,使得a[i] = key,若不存在,则返回-1(lowerbound函数);
  • 求最大的i的下一个元素的下标(c++中的upperbound函数),使得a[i] = key,若不存在,则返回-1
  • 求最大的i,使得a[i] = key,若不存在,则返回-1
  • 求最小的i,使得a[i] > key,若不存在,则返回-1
  • 求最大的i,使得a[i] < key,若不存在,则返回-1
#include 

using namespace std;
const int maxn = 100 + 10;

int cmp(const void *a, const void *b) {
    return *(int *) a - *(int *) b;
}

//普通的二分查找
int bs(int *arr,int L,int R,int target){
    while( L <= R){
        int mid = (L) + (R-L)/2;
        if(arr[mid] == target)
            return mid;
        if(arr[mid] > target)
            R = mid - 1;
        else
            L = mid + 1;
    }
    return -1; // not find
}

//求最小的i,使得a[i] = target,若不存在,则返回-1
//返回 如果有重复的 下界(比如1,2,2,2,3,4)查找2,返回1
int firstEqual(int arr[], int L, int R, int target) {
    while (L < R) {
        int mid = L + (R - L) / 2;
        if (arr[mid] < target)
            L = mid + 1;
        else
            R = mid;
    }
    if (arr[L] == target)
        return L;
    return -1;
}

//求最大的i的下一个元素的下标(c++中的upperbound函数),使得a[i] = target,若不存在,则返回-1
int lastEqualNext(int arr[], int L, int R, int target) {
    while (L < R) {
        int m = L + (R - L) / 2;
        if (arr[m] <= target) 
            L = m + 1;
        else
            R = m;
    }
    if (arr[L - 1] == target)
        return L;
    return -1;
}

//求最大的i,使得a[i] = target,若不存在,则返回-1
int lastEqual(int arr[], int L, int R, int target) {
    while (L < R) {
        int mid = L + ((R + 1 - L) >> 1);//向上取整
        if (arr[mid] <= target)
            L = mid;
        else
            R = mid - 1;
    }
    if (arr[L] == target)
        return L;
    return -1;
}

//求最小的i,使得a[i] > target,若不存在,则返回-1
int firstLarge(int arr[], int L, int R, int target) {
    while (L < R) {
        int m = L + ((R - L) >> 1);//向下取整
        if (arr[m] <= target)
            L = m + 1;
        else
            R = m;
    }
    if (arr[R] > target)
        return R;
    return -1;
}

//求最大的i,使得a[i] < target,若不存在,则返回-1
int lastSmall(int arr[], int L, int R, int target) {
    while (L < R) {
        int m = L + ((R + 1 - L) >> 1);//向上取整
        if (arr[m] < target)
            L = m;
        else
            R = m - 1;
    }
    if (arr[L] < target)
        return L;
    return -1;
}

int main() {
    //freopen("in.txt", "r", stdin);
    int n, a[maxn], v;
    scanf("%d", &n);
    for (int i = 0; i < n; i++)scanf("%d", &a[i]); //1 3 2 9 4 1 3 7 2 2
    scanf("%d", &v);   //input the number you need find
    qsort(a, n, sizeof(a[0]), cmp);                // 1 1 2 2 2 3 3 4 7 9
    printf("after sorted : \n");
    for (int i = 0; i < n; i++)printf("%d ", a[i]);

    printf("\n-------------test----------------");
    
    printf("\n%d\n", firstEqual(a, 0, n, v));  //output 2  第一个
    printf("%d\n", lastEqualNext(a, 0, n, v)); //output 4 + 1,最后一个的下一个
    printf("%d\n", lastEqual(a, 0, n, v));     //output 4  最后一个
    printf("%d\n", firstLarge(a, 0, n, v));    //output 5(第一个3大于2)
    printf("%d\n", lastSmall(a, 0, n, v));     //output 1(不是0)
    return 0;
}
/*
测试数据:
10
1 3 2 9 4 1 3 7 2 2
2
*/

输出
ACM常用模板(+模板题)(基础)_第1张图片


枚举排列

枚举排列的算法也有几个,包括刘汝佳书上的和经典的,还有做过的几个题LeetCode47。LeetCode46。

#include 

using namespace std;
const int maxn = 100 + 10;

void permutation(int *arr, int n, int cur){
    if(cur == n){  // 边界
        for(int i = 0; i < n; i++)
            printf("%d ",arr[i]);
        printf("\n");
    }
    else for(int i = 1; i <= n; i++){  //尝试在arr[cur]中填充各种整数
        bool flag = true;
        for(int j = 0; j < cur; j++)if(i == arr[j]){  // 如果i已经在arr[0]~arr[cur-1]中出现过,则不能选
            flag = false;
            break;
        }
        if(flag){
            arr[cur] = i;  //把i填充到当前位置
            permutation(arr, n, cur+1);
        }
    }
}

// 求 1 ~ n 的全排列,arr数组作为中间打印数组
int main(int argc, char const **argv)
{
    int a[maxn], n;
    scanf("%d", &n);
    permutation(a, n, 0);
    return 0;
}

//可重集的全排列
#include 
const int maxn = 100 + 10;

void permutation(int *arr,int *p,int n,int cur){
    if(cur == n){
        for(int i = 0; i < n; i++)
            printf("%d ",arr[i]);
        printf("\n");
    }else for(int i = 0; i < n; i++)if(!i || p[i] != p[i-1]){
        int c1 = 0, c2 = 0; 
        for(int j = 0; j < n; j++)
            if(p[j] == p[i]) // 重复元素的个数
                c1++;
        for(int j = 0; j < cur; j++)
            if(arr[j] == p[i]) // 前面已经排列的重复元素的个数 
                c2++;
        if(c2 < c1){
            arr[cur] = p[i];
            permutation(arr, p, n, cur+1);
        }
    }
}

int main(){
    int a[maxn], p[maxn] = {5, 6, 7, 5};  //可以有重复元素的全排列
    std::sort(p, p+4);
    permutation(a, p, 4, 0);
    return 0;
}

//全排列的非去重递归算法
#include 
using namespace std;
const int maxn = 100 + 10;

void permutation(int arr[], int cur, int n){
    if( cur == n){
        for(int i = 0; i < n; i++)
            printf("%d ", arr[i]);
        printf("\n");
    }
    else for(int i = cur; i < n; i++){
        swap(arr[i], arr[cur]);
        permutation(arr, cur+1, n);
        swap(arr[i], arr[cur]);
    }
}
int main(){
    int n, a[maxn];
    scanf("%d", &n);
    for(int i = 0; i < n; i++)
        scanf("%d", &a[i]);
    permutation(a, 0, n);
    return 0;
}


子集生成

增量构造法,位向量法,二进制法(常用)

#include 
using namespace std;
const int maxn = 100 + 10;

//打印0~n-1的所有子集
//按照递增顺序就行构造子集 防止子集的重复 
void print_subset(int *arr, int n, int cur){
    for(int i = 0; i < cur; i++)
        printf("%d ", arr[i]);
    printf("\n");
    int s = cur ? arr[cur-1] + 1 : 0;  //确定当前元素的最小可能值
    for(int i = s; i < n; i++){
        arr[cur] = i;
        print_subset(arr, n, cur+1);
    }
}
int main(){
    int n, arr[maxn];
    scanf("%d", &n);
    print_subset(arr, n, 0);
    return 0;
}

这个其实很简单,就是枚举每个位置01两种情况即可。

// 1~n 的所有子集:位向量法
#include 
const int maxn = 100 + 10;
using namespace std;
int bits[maxn];//位向量bits[i] = 1,当且仅当i在子集 A 中

void print_subset(int n,int cur){
    if(cur == n){
        for(int i = 0; i < cur; i++)
            if(bits[i])
                printf("%d ",i);
        printf("\n");
        return;
    }
    bits[cur] = 1;
    print_subset(n,cur + 1);
    bits[cur] = 0;
    print_subset(n,cur + 1);
}

int main() {
    int n;
    scanf("%d", &n);
    print_subset(n,0);
    return 0;
}

二进制枚举子集用的多,这里举个例子 n = 3;则要枚举0 - 7 对应的是有7个子集,每个子集去找有哪些元素print_subset中的 1<< i,也就是对应的那个位置是有元素的,例如1的二进制是0001也就是代表0位置有元素,00102,代表第一个位置是10100代表第2个位置上有元素,相应的1000 = 8对应第3个位置上有元素。
总结来说也就是对应1<< i对应i上是1(从0开始),其余位置是0。看图容易理解:
ACM常用模板(+模板题)(基础)_第2张图片

// 0 ~ n-1的所有子集:二进制法枚举0 ~ n-1的所有子集
#include 
const int maxn = 100 + 10;
using namespace std;
void print_subset(int n,int cur){
    //这一步其实就是判断 cur 的二进制的各个位上是不是1,如果是1,就输出对应的那个位置(位置从0开始)
    for(int i = 0; i < n; i++)
        if(1 & (cur >> i))
            printf("%d ",i);   
    printf("\n");
}
int main(int argc, char const** argv)
{
    int n;
    scanf("%d",&n);
    for(int i = 0; i < (1 << n); i++)
        print_subset(n,i);//枚举各子集对应的编码 0,1,2...pow(2,n) - 1
    return 0;
}

n皇后回溯

可以看这篇博客。

//n皇后问题:普通回溯法
#include 
const int maxn = 100 + 10;
using namespace std;
int sum,n,cnt; //解的个数,n皇后,递归次数
int C[maxn];

void Search(int cur){      //逐行放置皇后
    cnt++;
    if(cur == n)sum++;
    else for(int i = 0; i < n; i++){   //尝试在各列放置皇后
        bool flag = true;
        C[cur] = i;   //尝试把第cur行的皇后放在第i列//如果 等下不行的话 就下一个 i++
        for(int j = 0; j < cur; j++){ //检查是否和已经放置的冲突
            if(C[cur] == C[j] || C[cur] + cur == C[j] + j || cur - C[cur] == j - C[j]){//检查列,"副对角线","主对角线"
                flag = false;break;
            }
        }
        if(flag)Search(cur+1);
    }
}
int main(){
    scanf("%d",&n); //输入n皇后
    sum = cnt = 0;//解的个数 和 递归的次数
    Search(0);
    printf("%d %d\n",sum,cnt);
    return 0;
}

优化过的

// n皇后问题:优化的回溯法
#include 
const int maxn = 100 + 10;
using namespace std;

int sum,n,cnt;
int C[maxn];
bool vis[3][maxn];
int Map[maxn][maxn];//打印解的数组

//一般在回溯法中修改了辅助的全局变量,一定要及时把他们恢复原状
void Search(int cur){  //逐行放置皇后
    cnt++;
    if(cur == n){
        sum++;
        for(int i = 0; i < cur; i++)Map[i][C[i]] = 1;//打印解
        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; j++)printf("%d ",Map[i][j]);
            printf("\n");
        }
        printf("\n");
        memset(Map,0,sizeof(Map)); //还原
    }
    else for(int i = 0; i < n; i++){ //尝试在 cur行的 各 列 放置皇后
        if(!vis[0][i] && !vis[1][cur+i] && !vis[2][cur-i+n]){//判断当前尝试的皇后的列、主对角线
            vis[0][i] = vis[1][cur+i] = vis[2][cur-i+n] = true;
            C[cur] = i;//cur 行的列是 i
            Search(cur+1);
            vis[0][i] = vis[1][cur+i] = vis[2][cur-i+n] = false;//切记!一定要改回来
        }
    }
}

int main(){
    scanf("%d",&n);
    memset(vis,false,sizeof(vis));
    memset(Map,0,sizeof(Map));
    sum = cnt = 0;
    Search(0);
    printf("%d %d\n",sum,cnt);//输出 解决方案 和 递归次数
    return 0;
}

并查集

并查集详细讲解以及每一步的优化可以看这篇博客。

//题目连接 : http://poj.org/problem?id=1611
//题目大意 : 病毒传染,可以通过一些社团接触给出一些社团(0号人物是被感染的)问有多少人(0~n-1个人)被感染
#include 
const int maxn = 100000 + 10;

int parent[maxn], rank[maxn];  //parent[]保存祖先,rank记录每个'树的高度'

void init(){
    for(int i = 0; i < maxn; i++)parent[i] = i; //注意这里
    for(int i = 0; i < maxn; i++)rank[i] = 1;
}

//int findRoot(int v){
//    return parent[v] == v ? v : parent[v] = findRoot(parent[v]);
//}

// 非递归
int findRoot(int v){
    while(parent[v] != v){
        parent[v] = parent[parent[v]]; // 路径压缩
        v = parent[v];
    }
    return v;
}

void unions(int a, int b){
    int aRoot = findRoot(a);
    int bRoot = findRoot(b);
    if (aRoot == bRoot)
        return;
    if (rank[aRoot] < rank[bRoot])
        parent[aRoot] = bRoot;
    else if(rank[aRoot] > rank[bRoot]){ 
        parent[bRoot] = aRoot;
    }else{ 
        parent[aRoot] = bRoot;
        rank[bRoot]++;
    }
}

int is_same(int x,int y){ //检查是不是在同一个集合中
    return findRoot(x) == findRoot(y);
}

int main(){
    int n,m,k,x,root;
    while(~scanf("%d%d",&n,&m) && (n||m)){
        init();
        for(int i = 0; i < m; i++){
            scanf("%d%d",&k,&root);
            for(int j = 1; j < k; j++){
                scanf("%d",&x);
                unions(root,x);
            }
        }
        int sum = 1;
        for(int i = 1; i < n; i++)
            if(findRoot(i) == findRoot(0))
                sum++; //找和0是一个集合的
        printf("%d\n",sum);
    }
    return 0;
}

树状数组

树状数组的主要用于需要频繁的修改数组元素,同时又要频繁的查询数组内任意区间元素之和的时候。具体一些解释看图。
ACM常用模板(+模板题)(基础)_第3张图片ACM常用模板(+模板题)(基础)_第4张图片
所以计算2^k次方我们可以用如下代码

int lowbit(int x){
    return x&(-x); //或者 return x&(x^(x-1));
}

ACM常用模板(+模板题)(基础)_第5张图片
ACM常用模板(+模板题)(基础)_第6张图片

这里给出一个例题POJ2352Stars
题目意思就是给你一些星星的坐标,每个星星的级别是他左下方的星星的数量,要你求出各个级别的星星有多少个,看样例就懂了
ACM常用模板(+模板题)(基础)_第7张图片
题目中一个重要的信息就是输入是按照y递增,如果y相同则x递增的顺序给出的,所以,对于第i颗星星,它的level就是之前出现过的星星中,横坐标小于等于i的星星的数量。这里用树状数组来记录所有星星的x值。

  • 代码中有个小细节就是x++这是因为lowbit不能传值为0,否则会陷入死循环
#include 
#include 
const int maxn = 32000 + 10;
int n,c[maxn],level[15000+10];

//计算2^k
int lowbit(int x){
    return x&(-x);
}

//更新数组的值
void update(int i,int val){
    while(i < maxn){               //注意这里是最大的x,没有记录所以用maxn,不能用n
        c[i] += val;
        i += lowbit(i);           //不断的往上面更新
    }
}
//查询
int sum(int i){
    int s = 0;
    while(i > 0){
        s += c[i];
        i -= lowbit(i);           //不断的往下面加
    }
    return s;
}

int main(){
    int x,y;
    scanf("%d",&n);
    memset(c,0,sizeof(c));
    memset(level,0,sizeof(level));
    for(int i = 0; i < n; i++){
        scanf("%d%d",&x,&y);
        x++;                      //加入x+1,是为了避免0,X是可能为0的,LOWBIT无法处理0的情况,因为它的结果也是0,那么最终就是一个死循环
        level[sum(x)]++;
        update(x,1);              //上面的层都要+1个
    }
    for(int i = 0; i < n; i++)
        printf("%d\n",level[i]);
    return 0;
}


KMP,Sunday,BM

这三个算法解决的问题都是 : 有一个文本串 S,和一个模式串 P,现在要查找 P 在 S 中的位置,三种算法的思路这里限于篇幅不多说,这篇博客对着三种算法讲解的比较详细。
KMP的较直观的分析也可以看看我的另一篇博客

//题目连接 : http://acm.hdu.edu.cn/showproblem.php?pid=1711
//题目大意 : 找第二个数组在第一个数组中出现的位置,如果不存在,输出-1
#include 
const int maxn = 1000000 + 10;
int n,m,a[maxn], b[10000 + 10],nexts[10000 + 10];

void getNext(int *p,int next[]) {   //优化后的求next数组的方法 
    int len = m;
    next[0] = -1;    //next 数组中的 最大长度值(前后缀的公共最大长度) 的第一个 赋值为  -1  
    int k = -1,j = 0;
    while (j < len - 1) {
        if (k == -1 || p[j] == p[k]) { //p[k]表示前缀 p[j] 表示后缀
            k++; j++;
            if(p[j] != p[k])next[j] = k;
            else next[j] = next[k];   //因为不能出现p[j] = p[ next[j ]],所以当出现时需要继续递归,k = next[k] = next[next[k]]
        }
        else k = next[k];
    }
}

int KMPSerach(int *s, int *p) {
    int sLen = n,pLen = m;
    int i = 0, j = 0;
    while (i < sLen && j < pLen) {
        if (j == -1 || s[i] == p[j])i++, j++;
        else j = nexts[j];
    }
    if (j == pLen)return i - j;
    else return -1;
}

int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &n, &m);
        for(int i = 0; i < n; i++)scanf("%d", &a[i]);
        for(int i = 0; i < m; i++)scanf("%d", &b[i]);
        getNext(b,nexts); //获取next数组
        int ans = KMPSerach(a, b);
        if (ans != -1)printf("%d\n", ans + 1);
        else printf("-1\n");
    }
    return 0;
}

BM算法,主要是根据两个规则去执行
ACM常用模板(+模板题)(基础)_第8张图片

//题目链接:http://acm.hrbust.edu.cn/index.php?m=ProblemSet&a=showProblem&problem_id=1551
//题目大意:找一串字符中是否出现"bkpstor"这段字符

#include 
#include 
#include 
using namespace std;
const int maxn = 1000;

int last(char *p, char c) { //找到文本串的  "坏字符"  在模式串中的位置
    for (int i = strlen(p) - 1; i >= 0; i--)if (p[i] == c)return i;
    return -1;
}

int BM_index(char *T, char *p) {
    int n = strlen(T);
    int m = strlen(p);
    int i = m - 1, j = m - 1;
    while (i <= n - 1) {
        if (T[i] == p[j])
            if (j == 0)return i;
            else i--, j--;
        else {
            i = i + m - min(j, 1 + last(p, T[i]));//文本的不符合的那个 位置 串移动的步数
            j = m - 1;//模式串的新位置
        }
    }
    return -1;
}

int Sum(char *T, char *P, int s) {//输出文本串中包含模式串的数量
    int e = BM_index(T + s, P);
    return e == -1 ? 0 : 1 + Sum(T, P, s + e + 1);
}

//测试数据 : 123bkpstor456bkpstor67  
int main() {
    char s[maxn];
    while (~scanf("%s",s)) {
        int cnt = BM_index(s, "bkpstor");
        //printf("%d\n",Sum(s,"bkpstor",0));出现次数输出2
        if (cnt == -1)printf("Safe\n");
        else printf("Warning\n");
    }
    return 0;
}

Sunday算法也是两个规则
这里写图片描述

//题目链接:http://acm.hrbust.edu.cn/index.php?m=ProblemSet&a=showProblem&problem_id=1551
//题目大意: 找一串字符中是否出现"bkpstor"这段字符
#include 
#include 
#include 
const int maxn = 1000;
using namespace std;

int last(char *p, char c) {
    for (int i = strlen(p) - 1; i >= 0; i--)if (p[i] == c)return i;
    return -1;
}

int Sunday(char *s, char *p) {
    int sLen = strlen(s);
    int pLen = strlen(p);
    int i = 0, j = 0;
    while (i < sLen && j < pLen) {
        if (s[i] == p[j])i++, j++;
        else {
            int index = i + pLen - j;   // 字符串中右端对齐的字符
            if (last(p, s[index]) == -1) { i = index + 1; j = 0; }  //  没有在匹配串中出现则直接跳过
            else {
                i = index - last(p, s[index]); j = 0; //否则  其移动步长 = 匹配串中最  右端  的该字符到末尾的距离 + 1。
            }
        }
    }
    if (j == pLen)return i - j;
    return -1;
}

int main() {
    char s[maxn];
    while (~scanf("%s",s)) {
        int cnt = Sunday(s,"bkpstor");
        if (cnt == -1)printf("Safe\n");
        else printf("Warning\n");
    }
    return 0;
}


01背包,完全背包

背包问题可以分为很多种,这里只简单讨论01背包和完全背包,
后来改写的01背包: 博客
参考博客:

https://blog.csdn.net/liusuangeng/article/details/38374405(很详细)
https://blog.csdn.net/xp731574722/article/details/70766804
https://blog.csdn.net/na_beginning/article/details/62884939#reply
https://blog.csdn.net/u013445530/article/details/40210587

//题目连接 : http://acm.hdu.edu.cn/showproblem.php?pid=2602
#include 
#include 
#include 
using namespace std;
const int maxn = 1000+5;
int w[maxn],v[maxn],dp[maxn][maxn],vis[maxn];

//打印解
void print(int n,int C){
    for(int i = n; i > 1; i--){
        if(dp[i][C] == dp[i-1][C-w[i]] + v[i]){
            vis[i] = 1;
            C -= w[i];
        }
    }
    vis[1] = (dp[1][C] > 0) ? 1: 0;
}

int main(){
    freopen("in.txt","r",stdin);
    int n,C,T;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&C);
        for(int i = 1;i <= n; i++) scanf("%d",&v[i]);
        for(int i = 1;i <= n; i++) scanf("%d",&w[i]);
        memset(dp,0,sizeof(dp)); //dp[0][0~C]和dp[0~N][0]都为0,前者表示前0个物品无论装入多大的包中总价值都为0,后者表示体积为0的背包都装不进去。
        memset(vis,0,sizeof(vis));
        for(int i = 1; i <= n; i++){
            for(int j = 0;j <= C; j++){
                dp[i][j] = dp[i-1][j];  //如果j不大于v[i]的话就dp[i][j] = dp[i-1][j];
                if(j >= w[i])  dp[i][j] = max(dp[i][j],dp[i-1][j-w[i]]+v[i]);
            }
        }
        printf("%d\n",dp[n][C]); //n个物品装入C容量的包能获得的最大价值
        //print(n,C);
        //for(int i = 1; i <= n; i++)if(vis[i])printf("%d ",i); //输出选中的物品
    }
    return 0;
}
#include 
#include 
#include 
const int maxn = 1000+5;
using namespace std;

int w[maxn],v[maxn],dp[maxn][maxn];

int main(){
    int T,n,C;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&C);
        for(int i = 1; i <= n; i++)scanf("%d",&v[i]);
        for(int i = 1; i <= n; i++)scanf("%d",&w[i]);
        memset(dp,0,sizeof(dp));  //dp全部初始化为0
        for(int i = n;i >= 1; i--){
            for(int j = 0; j <= C; j++){
                dp[i][j] = (i == n ? 0 : dp[i+1][j]);
                if(j >= w[i])dp[i][j] = max(dp[i][j],dp[i+1][j-w[i]]+v[i]);
            }
        }
        printf("%d\n",dp[1][C]);
    }
    return 0;
}
#include 
#include 
#include 
const int maxn = 1000+5;
using namespace std;

int w[maxn],v[maxn],dp[maxn];

int main(){
    int T,n,C;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&C);
        for(int i = 1; i <= n; i++)scanf("%d",&v[i]);
        for(int i = 1; i <= n; i++)scanf("%d",&w[i]);
        memset(dp,0,sizeof(dp));
        for(int i = 1; i <= n; i++){
            for(int j = C;j >= 0; j--){
                if(j >= w[i])dp[j] = max(dp[j],dp[j-w[i]]+v[i]);
            }
        }
        printf("%d\n",dp[C]);
    }
    return 0;
}

完全背包和01背包的差别在于每个物品的数量有无数个,在考虑第i件物品的时候,不是考虑选不选这件,而是选多少件可以达到最大价值
这里写图片描述

for (int i = 1; i < n; i++){
    for (int j = 1; j <= v; j++){
        for (int k = 0; k*c[i] <= j; k++){
         if(c[i] <= j) dp[i][j] = max{dp[i-1][j],dp[i-1][j - k * c[i]] + k * w[i]};/*要么不取,要么取0件、取1件、取2件……取k件*/
         else dp[i][j] = dp[i-1][j]/*继承前i个物品在当前空间大小时的价值*/
        }
    }   
}

优化:
ACM常用模板(+模板题)(基础)_第9张图片
注意完全背包的顺序问题 :因为每种背包都是无限的。当我们把i从1到N循环时,dp[v]表示容量为v在前i种背包时所得的价值,这里我们要添加的不是前一个背包,而是当前背包。所以我们要考虑的当然是当前状态。为01背包中要按照v=V…0的逆序来循环。这是因为要保证第i次循环中的状态dp[i][v]是由状态dp[i-1] [v-c[i]]递推而来。这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的 子结果dp[i-1][v-c[i]]。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果dp[i][v-c[i]],所以就可以并且必须采用v=0…V的顺序循环。
完全背包还有考虑是否要求恰好放满背包等问题,可以好好研究:
ACM常用模板(+模板题)(基础)_第10张图片
参考博客:
https://blog.csdn.net/Thousa_Ho/article/details/78156678
https://wenku.baidu.com/view/eea4a76b0b1c59eef8c7b497.html
https://www.cnblogs.com/shihuajie/archive/2013/04/27/3046458.html

#include 
#include 
#include 

using namespace std;
const int maxn = 50000 + 10;
const int INF = -0X7ffff;
int w[maxn],v[maxn],dp[maxn];

int main(){
    int T,n,C;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&C);
        for(int i = 1; i <= C; i++)dp[i] = INF;
        dp[0] = 0;
        for(int i = 0; i < n; i++)scanf("%d%d",&w[i],&v[i]);
        for(int i = 0; i < n; i++){
            for(int j = w[i]; j <= C; j++){ //从前往后递推,这样才能保证一种物品可以被使用多次
                dp[j] = max(dp[j],dp[j-w[i]] + v[i]);
            }
        }
        if(dp[C] > 0)printf("%d\n",dp[C]);
        else printf("NO\n");
    }
    return 0;
}

最长(不)上升或下降子序列

Java编写与解释的博客。
先说O(N*N)的解法,第i个元素之前的最长递增子序列的长度要么是1(单独成一个序列),要么就是第i-1个元素之前的最长递增子序列加1,这样得到状态方程:

LIS[i] = max{1,LIS[k]+1}  (∀k<i,arr[i] > arr[k])

这样arr[i]才能在arr[k]的基础上构成一个新的递增子序列。

//题目连接 : http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=17
#include 
#include 
#include 
using namespace std;
const int maxn = 10000 + 10;

int dp[maxn]; /* dp[i]记录到[0,i]数组的LIS */
int maxx;/* LIS长度,初始化为1 */

int LIS(char *arr, int n) {
    for (int i = 0; i < n; i++) {
        dp[i] = 1;
        for (int j = 0; j < i; j++) // 注意i只遍历比它小的元素
            if (arr[j] < arr[i]) dp[i] = max(dp[i], dp[j] + 1);  //改成arr[j] <= arr[i]就可以求非减的
        maxx = max(maxx, dp[i]);
    }
    return maxx;
}

/* 递归输出LIS,因为数组dp还充当了“标记”作用 */
void printLis(char *arr, int index) {
    bool isLIS = false;
    if (index < 0 || maxx == 0)return;
    if (dp[index] == maxx) {
        --maxx;
        isLIS = true;
    }
    printLis(arr, --index);
    if (isLIS) printf("%c ", arr[index + 1]);
}

int main() {
    char s[maxn];
    int n;
    scanf("%d\n",&n);
    while(n--){
        maxx = 1;
        scanf("%s",s);
        printf("%d\n",LIS(s,strlen(s)));
        //printLis(s,strlen(s)-1);printf("\n");
    }
    return 0;
}

然后就是nlog(n)的解法:
参考博客:
https://segmentfault.com/a/1190000002641054
https://blog.csdn.net/joylnwang/article/details/6766317

//题目连接 : http://poj.org/problem?id=3903
#include 
#include 
#include 
#include 
using namespace std;

const int INF = 0x3f3f3f3f;
const int maxn = 100000 + 5;
int a[maxn], dp[maxn], pos[maxn], fa[maxn];
vector<int> ans;

//用于最长非递减子序列种的lower_bound函数
int cmp(int a,int b){
    return a <= b;
}

//最长上升子序列
//dp[i]表示长度为i+1的上升子序列的最末尾元素 找到第一个比dp末尾大的来代替 
int LIS(int n){
    memset(dp, 0x3f, sizeof(dp));
    pos[0] = -1;
    int i,lpos;
    for (i = 0; i < n; ++i){
        dp[lpos = (lower_bound(dp, dp + n, a[i]) - dp)] = a[i];
        pos[lpos] = i;    // *靠后打印
        fa[i] = (lpos ? pos[lpos - 1] : -1);
    }
    n = lower_bound(dp, dp + n, INF) - dp;
    for (i = pos[n - 1]; ~fa[i]; i = fa[i]) ans.push_back(a[i]);
    ans.push_back(a[i]);
    return n;
}

//非递减的LIS
int LISS(int n){
    memset(dp, 0x3f, sizeof(dp));
    pos[0] = -1;
    int i,lpos;
    for (i = 0; i < n; i++){
        dp[lpos = (lower_bound(dp, dp + n, a[i],cmp) - dp)] = a[i]; //注意这里cmp
        pos[lpos] = i;    // *靠后打印
        fa[i] = (lpos ? pos[lpos - 1] : -1);
    }
    n = lower_bound(dp, dp + n, INF) - dp;
    for (i = pos[n - 1]; ~fa[i]; i = fa[i]) ans.push_back(a[i]);
    ans.push_back(a[i]);
    return n;
}

int main(){
    // freopen("in.txt","r",stdin);
    int n;
    while(~scanf("%d",&n)){
        ans.clear();
        for(int i = 0;i < n; i++)scanf("%d",&a[i]);
        printf("%d\n",LIS(n));
       // for(int i = ans.size()-1; i >= 0; i--) printf("%d ",ans[i]);printf("\n"); //路径打印
    }
    return 0;
}

最长公共子序列

最长公共子序列利用动态规划的方式解决,具有最优子结构性质,和重叠子问题性质,dp[i][j]记录序列XiYi的最长公共子序列的长度,当i = 0j =0 时,空序列时XiYi的最长公共子序列,其余情况如下
ACM常用模板(+模板题)(基础)_第11张图片
这里也给出一篇讲的好的博客

//题目连接 : http://poj.org/problem?id=1458
#include 
#include 
#include 
using namespace std;
const int maxn = 1000 + 10;
int dp[maxn][maxn];
int path[maxn][maxn]; //记录路径

int LCS(char *s1,char *s2){
    int len1 = strlen(s1)-1,len2 = strlen(s2)-1;//注意例如 abcfbc的strlen(s1)为7,所以是strlen(s1) - 1
    for(int i = 0; i <= len1; i++) dp[i][0] = 0;
    for(int i = 0; i <= len2; i++) dp[0][i] = 0;
    for(int i = 1; i <= len1; i++){
        for(int j = 1; j <= len2; j++)
            if(s1[i] == s2[j]){
                dp[i][j] = dp[i-1][j-1] + 1;
                path[i][j] = 1;
            }
            else if(dp[i-1][j] >= dp[i][j-1]) {
                dp[i][j] = dp[i-1][j];
                path[i][j] = 2;
            }
            else {
                dp[i][j] = dp[i][j-1];
                path[i][j] = 3;
            }
    }
    return dp[len1][len2];
}

//打印解
void print(int i,int j,char *s){
    if(i == 0 || j == 0)return ;
    if(path[i][j] == 1){
        print(i-1,j-1,s);
        printf("%c ",s[i]);
    }else if(path[i][j] == 2){
        print(i-1,j,s);
    }else print(i,j-1,s);
}

int main(){
    char s1[maxn],s2[maxn];
    while(~scanf("%s%s",s1+1,s2+1)){ //注意s1[0]不取-注意例如 abcfbc的strlen(s1)为7
        memset(path,0,sizeof(path));
        printf("%d\n",LCS(s1,s2));
        //print(strlen(s1)-1,strlen(s2)-1,s1);
    }
    return 0;
}


拓扑排序

有向无环图上的一种排序方式,我的这篇博客也讲解了一下。可以两种写法:

//题目链接 : https://vjudge.net/contest/169966#problem/O
#include 

using namespace std;
const int maxn = 100 + 10;

int n, m;
int in[maxn];
vector<int>G[maxn];

void topo(){
    queue<int>q;
    for(int i = 1; i <= n ; i++)
        if(in[i] == 0)
            q.push(i);
    bool flag = true;
    while(!q.empty()){
        int cur = q.front();
        q.pop();
        if(flag){
            printf("%d",cur); 
            flag = false;
        }
        else
            printf(" %d",cur);
        for(int i = 0; i < G[cur].size(); i++){ 
            if(--in[G[cur][i]] == 0) 
                q.push(G[cur][i]); 
        }
    } 
}

int main(int argc, char const **argv)
{
    int from, to;
    while(~scanf("%d%d", &n, &m) && (n || m)){
        memset(in, 0, sizeof(in));
        for(int i = 1; i <= n; i++)
            G[i].clear();
        for(int i = 0; i < m; i++){
            scanf("%d%d", &from, &to);
            in[to]++; //度
            G[from].push_back(to);
        }
        topo();
        printf("\n");
    }
    return 0;
}

#include 
#include 
#include 
using namespace std;
#define maxn  5005

typedef struct { ///邻接表实现
    int next_arc;///下一条边
    int point;
} Arc; ///边的结构体,

Arc arc[maxn];///储存每条边
int node[maxn],vis[maxn],top[maxn];///储存每个顶点,node[i]表示第i个顶点指向的第一条边在arc中的位置
int n, m, t;

void dfs(int v) {
    for (int i = node[v]; i != -1; i = arc[i].next_arc) {
        if (!vis[arc[i].point]) {
            dfs(arc[i].point);
        }
    }
    vis[v] = 1;
    top[--t] = v;
}


int main() {
    int a, b;
    while (cin >> n >> m && (m || n)) {
        memset(node, -1, sizeof(node));
        memset(vis, 0, sizeof(vis));
        for (int i = 1; i <= m; i++) {
            cin >> a >> b;
            arc[i].next_arc = node[a];///第一次是出发点,以后是下一个
            arc[i].point = b;
            node[a] = i;
            vis[arc[i].point] = 0;
        }
        t = n;
        for (int j = 1; j <= n; j++) if (!vis[j]) dfs(j);
        for (int i = 0; i < n - 1; i++)
            cout << top[i] << " ";
        cout << top[n - 1] << endl;
    }
    return 0;
}

欧拉路径和回路

首先看定义 :

欧拉回路:

  • (1) 图G是连通的,不能有孤立点存在。
  • (2) 对于无向图来说度数为奇数的点个数为0,对于有向图来说每个点的入度必须等于出度。

欧拉路径:

  • (1) 图G是连通的,无孤立点存在。
  • (2) 对于无向图来说,度数为奇数的的点可以有2个或者0个,并且这两个奇点其中一个为起点另外一个为终点。对于有向图来说,可以存在两个点,其入度不等于出度,其中一个入度比出度大1,为路径的起点;另外一个出度比入度大1,为路径的终点。
    判断连通的方式有DFS或者并查集,然后再判断一下是否满足条件即可,之前做的欧拉回路的几个好题在我的github仓库
#include 

const int maxn = 1000 + 5;
using namespace std;

bool vis[maxn];
int in[maxn];
int n, m;
vector<int>G[maxn];

void dfs(int u){
    vis[u] = 1;
    for(int i = 0; i < G[u].size(); i++){ 
        if(!vis[G[u][i]])
            dfs(G[u][i]);
    }
}

int main(){
    //freopen("in.txt","r",stdin);
    int from, to;
    while(scanf("%d", &n) != EOF && n){
        scanf("%d", &m);
        memset(vis, 0, sizeof(vis));
        memset(in, false, sizeof(in));
        for(int i = 1; i <= n; i++)
            G[i].clear();
        bool flag = true;
        for(int i = 0; i < m; i++){
            scanf("%d%d",&from, &to);
            G[from].push_back(to);
            G[to].push_back(from);
            in[from]++;
            in[to]++;
        }
        dfs(1);
        for(int i = 1; i <= n; i++)
            if(in[i] % 2 != 0)
                flag = false;
        for(int i = 1; i <= n; i++)
            if(!vis[i])
                flag = false;
        cout << (flag ? 1 : 0) << endl;
    }
    return 0;
}


#include 

const int maxn = 1000 + 5;
using namespace std;

int n, m, parent[maxn],ranks[maxn],in[maxn];

void init(){
    for(int i = 0; i < maxn; i++)
        parent[i] = i;  
    for(int i = 0; i < maxn; i++)
        ranks[i] = 1;
}

//int findRoot(int v){
//    return parent[v] == v ? v : parent[v] = findRoot(parent[v]);
//}

int findRoot(int v){ 
    while(parent[v] != v){ 
        parent[v] = parent[parent[v]];
        v = parent[v];
    }
    return v;
}

void unions(int a, int b){
    int aRoot = findRoot(a);
    int bRoot = findRoot(b);
    if (aRoot == bRoot)
        return;
    if (ranks[aRoot] < ranks[bRoot])
        parent[aRoot] = bRoot;
    else if(ranks[aRoot] > ranks[bRoot]){
        parent[bRoot] = aRoot;
    }else { 
        parent[aRoot] = bRoot;
        ranks[bRoot]++;
    }
}


int main(){
    //freopen("in.tat","r",stdin);
    int u, v;
    while(scanf("%d", &n) != EOF && n){
        init();
        scanf("%d", &m);
        memset(in,0,sizeof(in));
        for(int i = 0; i < m; i++){
            scanf("%d%d",&u, &v);
            unions(u, v);
            in[u]++;
            in[v]++;
        }
        bool flag = true;
        int temp = findRoot(1);
        for(int i = 1; i <= n; i++)
            if(findRoot(i) != temp)
                flag = false;
        for(int i = 1; i <= n; i++)
            if(in[i] % 2 != 0)
                flag = false; //判断度
        cout << (flag ? 1 : 0) << endl;
    }
    return 0;
}

搜索

这里举几个比较好的题目: POJ3984,这个题目要求记录BFS最短路的路径,我这里使用三种方法:

  • BFS+在结构体中记录路径
  • BFS+记录路径
  • DFS加回溯法
//题目链接;http://poj.org/problem?id=3984
//题目大意:给你一个5*5的矩阵,让你找一条路,从左上点,到右下点
//解题思路:利用BFS求解最短路,利用vector记录路径

//BFS+在结构体中记录路径
#include 
#include 
#include 
#include 
#include 
#include 
#pragma warning(disable : 4996)
using namespace std;
const int maxn = 100 + 10;

int n;
int map[maxn][maxn];
bool vis[maxn][maxn];
int dir[4][2] = {{-1,0},{0,1},{1,0},{0,-1}};//对应上,右,下,左

struct Road {//路径记录
    int x,y,d;//d表示方向
    Road() {}//默认的构造函数
    Road(int a,int b,int c):x(a),y(b),d(c) {}//带参数的构造函数
};

struct node {//节点类型
    int x,y;
    vector<Road> v;//记录路径的结构体
};

bool check(node u) {
    if (!vis[u.x][u.y] && u.x >= 0 && u.x < n && u.y >= 0 && u.y < n && map[u.x][u.y] != 1)
        return true;
    else
        return false;
}

void BFS(int x, int y) {
    queue<node>q;
    node now,next;
    now.x = x;
    now.y = y;
    now.v.push_back(Road(x,y,-1));
    q.push(now);
    while (!q.empty()) {
        now = q.front();
        q.pop();
        if (now.x == n - 1 && now.y == n-1) {
            for(int i = 0; i < now.v.size(); i++){
                printf("(%d, %d)\n",now.v[i].x,now.v[i].y);
            }
            break;
        }
        for (int i = 0; i < 4; i++) {
            next.x = now.x + dir[i][0];
            next.y = now.y + dir[i][1];
            if (check(next)) {
                vis[next.x][next.y] = true;
                next.v = now.v;
                next.v[now.v.size()-1].d = i;
                next.v.push_back(Road(next.x,next.y,-1));
                q.push(next);
            }
        }
    }
}

int main() {
    //freopen("in.txt", "r", stdin);
    flag = false;
    n = 5;
    memset(vis, 0, sizeof(vis));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            scanf("%d", &map[i][j]);
        }
    }
    BFS(0, 0);
    return 0;
}
//BFS+记录路径
#include 
#include 
#include 

using namespace std;
const int maxn = 100 + 10;

int dir[4][2] = {{-1,0},{0,1},{1,0},{0,-1}};
bool vis[maxn][maxn];
int map[maxn][maxn];

struct Node {
    int x,y,pre;
};

Node m_queue[maxn];

bool Check(int x,int y){
    if(!map[x][y] && !vis[x][y] && x >= 0 && x < 5&&y >= 0 && y < 5)return true;
    else return false;
}

void print(int u){
    if(m_queue[u].pre != -1){
        print(m_queue[u].pre);
        printf("(%d, %d)\n",m_queue[u].x,m_queue[u].y);
    }
}

void BFS(int x,int y){
    int head = 0,rear = 1;
    m_queue[head].x = x;
    m_queue[head].y = y;
    m_queue[head].pre = -1;
    vis[x][y] = true;
    while(head < rear){
        for(int i = 0; i < 4; i++){
            int xx = m_queue[head].x + dir[i][0];
            int yy = m_queue[head].y + dir[i][1];
            if(Check(xx,yy)){
                vis[xx][yy] = 1;
                m_queue[rear].x = xx;
                m_queue[rear].y = yy;
                m_queue[rear].pre = head;
                rear++;
            }
            if(xx == 4&&yy == 4)print(rear - 1);
        }
        head++;//出队
    }
}


int  main(){
    //freopen("in.txt","r",stdin);
    int n = 5;
    memset(vis,0,sizeof(vis));
    for(int i = 0; i < n; i++){
        for(int j = 0; j < n; j++){
            scanf("%d",&map[i][j]);
        }
    }
    printf("(0, 0)\n");
    BFS(0,0);
    return 0;
}
//DFS加回溯法
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;
const int maxn = 100+10;
const int INF = 0x3f3f3f3f;

int n,k,ans = INF;
int map[maxn][maxn];
bool vis[maxn][maxn];
int dir[4][2] = {{-1,0},{0,1},{1,0},{0,-1}};

struct node {
    int x,y;
};
vector<node>dict;
stack<node>s;

void memcpy(stack<node>s){
    dict.clear();
    while(!s.empty()){
        dict.push_back(s.top());
        s.pop();
    }
}

bool Check(int x,int y){
    if(!vis[x][y] && map[x][y] != 1 && x >= 0 && x < n && y >= 0 && y < n)return true;
    else return false;
}

void DFS(int x,int y,int step){//深刻理解递归的含义
    node now;
    now.x = x;
    now.y = y;
    s.push(now);
    if(now.x == n - 1 &&now.y == n - 1){
        if(step < ans){//记录最短的路径
            ans = step;
            memcpy(s);
        }
    }
    for(int i = 0; i < 4; i++){
        int xx = now.x + dir[i][0];
        int yy = now.y + dir[i][1];
        if(Check(xx,yy)){
            vis[xx][yy] = true;
            DFS(xx,yy,step + 1);
            vis[xx][yy] = false;//回溯
        }
    }
    s.pop();//反弹的时候重新还原栈
}

int main(){
    //freopen("in.txt","r",stdin);
    n = 5;
    memset(vis,false,sizeof(vis));
    for(int i = 0; i < n; i++){
        for(int j = 0; j < n; j++){
            scanf("%d",&map[i][j]);
        }
    }
    DFS(0,0,0);
    for(int i = dict.size() - 1; i >= 0; i--){
        printf("(%d, %d)\n",dict[i].x,dict[i].y);
    }
    return 0;
}

这里再给出DFS的一个好题,印象比较深刻的

//题目链接:https://vjudge.net/contest/169966#problem/V
//题目大意: 有一个正方形的 战场看,边长为1000,西南方向在坐标原点,战场上有n 个敌人,坐标x,y,攻击范围y
           //要你从西边界出发,东边界离开,求西边界和东边界上的坐标,坐标要尽量靠北,如果不能到达东边界输出IMPOSSIBLE
//解题思路: 按照 刘汝佳书上的思路,先判断有无解,从上边界开始DFS如果可以一直到达下边界,则相当于堵住了,无解
          //否则,也是从上边界开始,dfs和东西边界 相连的圆(如果没有就是1000),找到最南边的圆的 与边界相交的那个点就 0k
          //主要还是DFS的运用
#include 
#include 
#include 
#include 
#include 

const int maxn = 1000 + 10;
const double W = 1000.0;
using namespace std;

double x[maxn], y[maxn], r[maxn],Left,Right;
bool vis[maxn];
int n;

bool isinterect(int u, int v) {
    return sqrt((x[u] - x[v])*(x[u] - x[v]) + (y[u] - y[v])*(y[u] - y[v])) <= r[u] + r[v];
}

void check_circle(int u) {
    if (x[u] <= r[u])Left = min(Left, y[u] - sqrt(r[u] * r[u] - x[u] * x[u]));
    if (x[u] + r[u] >= W)Right = min(Right, y[u] - sqrt(r[u] * r[u] - (W-x[u]) * (W-x[u])));
}

bool dfs(int u) {// 检查是否有解
    if (vis[u] == true)return false;
    vis[u] = true;
    if (y[u] <= r[u])return true;  //刚好碰到边界  以及  相切都可以 
    for (int i = 0; i < n; i++)if (isinterect(u,i) && dfs(i))return true;// if (&& dfs(i) && isinterect(u,i) )是错的,要先判断是否相交然后再dfs
    check_circle(u);
    return false;
}

int main() {
    //freopen("in.txt", "r", stdin);
    while (scanf("%d", &n) != EOF) {
        memset(vis, false, sizeof(vis));
        Left = Right = W;
        bool flag = true;
        for (int i = 0; i < n; i++) {
            scanf("%lf%lf%lf", &x[i], &y[i], &r[i]);
        }
        for (int i = 0; i < n; i++) {
            if (y[i] + r[i] >= W) {  //从上往下  DFS
                if (dfs(i)) { flag = false; break; }//如果能够从最上面 DFS 到最下面 则无解
            }
        }
        if (flag) printf("0.00 %.2lf 1000.00 %.2lf\n", Left,Right);
        else printf("IMPOSSIBLE\n");
    }
    return 0;
}

最小生成树

直接给出模板primkruskal(prim是堆优化的)

另外,我的另外一篇博客也总结了这两个算法。

//Prim模板
//题目链接 : http://poj.org/problem?id=1287
#include 
#include 
#include 
#include 

using namespace std;
const int maxn = 100 + 10;
const int INF = 1<<30;

struct Node{
    int v,w;
    Node (){}
    Node (int v,int w):v(v),w(w){}
    bool operator < (const Node&rhs ) const {
        return rhs.w < w;
    }
};

int n,d[maxn];
bool vis[maxn];
int Map[maxn][maxn];

void init(){
    for(int i = 0; i < maxn; i++)vis[i] = false;
    for(int i = 0; i < maxn; i++)d[i] = INF;
}

int Prim(int s){
    priority_queue<Node>q;
    q.push(Node(s,0));
    int ans = 0;
    while(!q.empty()){
        Node Now = q.top(); q.pop();
        int v = Now.v;
        if(vis[v]) continue;
        ans += Now.w;
        vis[v] = true;
        for(int i = 1; i <= n; i++){
            int w2 = Map[v][i];
            if(!vis[i] && d[i] > w2){
                d[i] = w2;
                q.push(Node(i,d[i]));
            }
        }
    }
    return ans;
}

int main(){
    //freopen("in.txt","r",stdin);
    int m,a,b,c;
    while(~scanf("%d",&n)&&n){
        scanf("%d",&m);
        init();
        for(int i = 1; i <= n; i++){
            Map[i][i] = INF;
            for(int j = 1; j < i; j++)Map[i][j] = Map[j][i] = INF;
        }
        for(int i = 0; i < m; i++){
            scanf("%d%d%d",&a,&b,&c);
            if(c < Map[a][b])Map[a][b] = Map[b][a] = c; //注意重边,取小的
        }
        printf("%d\n",Prim(1));
    }
    return 0;
}
//题目链接 : http://poj.org/problem?id=1287
//kruskal模板
#include 
#include 
#include 
#include 

using namespace std;
const int maxn = 1e5 + 10;

int Fa[maxn],Rank[maxn];

void init(){
    for(int i = 0; i <= maxn; i++)Fa[i] = i;
    for(int i = 0; i <= maxn; i++)Rank[i] = 1;
}

int Find(int v){
    return Fa[v] == v ? v : Fa[v] = Find(Fa[v]);
}

void Union(int x, int y){
    int fx = Find(x);
    int fy = Find(y);
    if (fx == fy)return;
    if (Rank[fx] < Rank[fy])
        Fa[fx] = fy;
    else{
        Fa[fy] = fx;
        if (Rank[fx] == Rank[fy])Rank[fy]++;
    }
}

struct Edge{
    int u,v,w;
    Edge(){}
    Edge(int u,int v,int w):u(u),v(v),w(w){}
}edge[maxn*2];


int cmp(const void *a,const void *b){
    Edge *aa = (Edge*)a;
    Edge *bb = (Edge*)b;
    return aa->w > bb->w ? 1 : -1; //降序
}

int krusal(int n,int m){
    qsort(edge,m,sizeof(edge[0]),cmp);
    int ans = 0;
    int cnt = 0;
    for(int i = 0; i < m; i++){
        int u = edge[i].u;
        int v = edge[i].v;
        if(Find(u) != Find(v)){
            Union(u,v);
            cnt++;
            ans += edge[i].w;
        }
        if(cnt == n-1)break;
    }
    return ans;
}

int main(){
    //freopen("in.txt","r",stdin);
    int n,m;
    while(~scanf("%d",&n)&&n){
        scanf("%d",&m);
        init();
        for(int i = 0; i < m; i++) scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w); //注意这题有重边但是没事
        printf("%d\n",krusal(n,m));
    }
    return 0;
}

最短路

dijkstrabellman_fordfloydSPFA
最经典的Hdu1874畅通工程续

//堆优化dijkstra
#include 
using namespace std;
const int maxn = 1e4 + 10;
const int INF = 1e9;

struct Node{
    int v,w;
    Node(int v,int w):v(v),w(w){}
    bool operator < (const Node&rhs) const {
        return rhs.w < w;
    }
};

vector<Node>G[maxn];
bool vis[maxn];
int d[maxn];
int n,m;

void init(){
    for(int i = 0; i < maxn; i++)G[i].clear();
    for(int i = 0; i < maxn; i++)vis[i] = false;
    for(int i = 0; i < maxn; i++)d[i] = INF;
}

int dijkstra(int s,int e){ //传入起点终点
    priority_queue<Node>q;
    q.push(Node(s,0));
    d[s] = 0;
    while(!q.empty()){
        Node now = q.top(); q.pop();
        int v = now.v;
        if(vis[v])continue;
        vis[v] = true;
        for(int i = 0; i < G[v].size(); i++){
            int v2 = G[v][i].v;
            int w = G[v][i].w;
            if(!vis[v2] && d[v2] > w+d[v]){
                d[v2] = w+d[v];
                q.push(Node(v2,d[v2]));
            }
        }
    }
    return d[e];
}

int main(){
    //freopen("in.txt","r",stdin);
    int s,e;
    while(~scanf("%d%d",&n,&m)){
        init();
        for(int i = 0; i < m; i++){
            int x, y, z;
            scanf("%d%d%d", &x, &y, &z);
            G[x].push_back(Node(y,z));
            G[y].push_back(Node(x,z));
        }
        scanf("%d%d",&s,&e);
        int ans = dijkstra(s,e);
        if(INF != ans)printf("%d\n",ans);
        else printf("-1\n");
    }
    return 0;
}
#include 
const int maxn = 1000;
#define INF 0x1f1f1f1f
using namespace std;
bool flag[maxn];
int graph[maxn][maxn],low[maxn];

void DIJ(int n, int s) {
    memset(flag, false, sizeof(flag));
    flag[s] = true;
    for (int i = 0; i < n; i++)low[i] = graph[s][i];
    for (int i = 1; i < n; i++) {
        int min = INF, p;
        for (int j = 0; j < n; j++)
            if (!flag[j] && min > low[j]) {
                min = low[j];
                p = j;
            }
        flag[p] = true;
        for (int j = 0; j < n; j++)
            if (!flag[j] && low[j] > graph[p][j] + low[p])
                low[j] = graph[p][j] + low[p];
    }
}

int main() {
    int n, m, begin, t;
    while (~scanf("%d%d",&n,&m)) {
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++)
                if (i == j)
                    graph[i][j] = 0;
                else
                    graph[i][j] = INF;
        for (int i = 1; i <= m; i++) {
            int x, y, z;
            scanf("%d%d%d", &x, &y, &z);
            if (z < graph[x][y]) graph[x][y] = graph[y][x] = z;
        }
        cin >> begin >> t;
        DIJ(n, begin);
        if (low[t] < INF) cout << low[t] << endl;
        else cout << "-1" << endl;
    }
    return 0;
}
#include 
#define INF 0x1f1f1f1f
using namespace std;
const int maxn = 1000;
vector<pair<int, int> >Edge[maxn];

int n, m, dist[maxn];
void init() {
    for (int i = 0; i < maxn; i++)Edge[i].clear();
    for (int i = 0; i < maxn; i++)dist[i] = INF;
}

int main() {
    int s, t;
    while (cin >> n >> m) {
        init();
        for (int i = 0; i < m; i++) {
            int x, y, z;
            cin >> x >> y >> z;
            Edge[x].push_back(make_pair(y, z));
            Edge[y].push_back(make_pair(x, z));
        }
        cin >> s >> t;
        priority_queue<pair<int, int>>q;
        dist[s] = 0;
        q.push(make_pair(-dist[s], s));
        while (!q.empty()) {
            int now = q.top().second;
            q.pop();
            for (int i = 0; i < Edge[now].size(); i++) {
                int v = Edge[now][i].first;
                if (dist[v] > dist[now] + Edge[now][i].second) {
                    dist[v] = dist[now] + Edge[now][i].second;
                    q.push(make_pair(-dist[v], v));
                }
            }
        }
        if (dist[t] < INF)cout << dist[t] << endl;
        else cout << "-1" << endl;
    }
    return 0;
}
//(3)bellman_ford
#include 
#define maxn 1000
#define INF 0x1f1f1f1f
using namespace std;

struct Edge {
    int u, v;
    int weight;
};
Edge edge[maxn * 2];

int dist[maxn];

void bellman_ford(int s, int n, int m) {
    for (int i = 0; i < n; i++) dist[i] = INF;
    dist[s] = 0;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < 2 * m; j++)
            if (dist[edge[j].u] > dist[edge[j].v] + edge[j].weight)
                dist[edge[j].u] = dist[edge[j].v] + edge[j].weight;
}

int main() {
    int n, m, s, t;
    while (cin >> n >> m) {
        for (int i = 0; i < m; i++) {
            scanf("%d%d%d", &edge[i].u, &edge[i].v, &edge[i].weight);
            edge[i + m].v = edge[i].u;
            edge[i + m].u = edge[i].v;
            edge[i + m].weight = edge[i].weight;
        }
        cin >> s >> t;
        bellman_ford(s, n, m);
        if (dist[t] < INF) cout << dist[t] << endl;
        else cout << "-1" << endl;
    }
    return 0;
}
//(4)floyd
#include 
#define maxn 1000
#define INF 0x1f1f1f1f
using namespace std;

int graph[maxn][maxn];
int low[maxn];

void floyd(int n) {
    for (int k = 0; k < n; k++)
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++)
                graph[i][j] = min(graph[i][j], graph[i][k] + graph[k][j]);
}

int main() {
    int n, m, s, t;
    while (cin >> n >> m) {
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++)
                if (i == j)
                    graph[i][j] = 0;
                else
                    graph[i][j] = INF;
        for (int i = 1; i <= m; i++) {
            int x, y, z;
            scanf("%d%d%d", &x, &y, &z);
            if (graph[x][y] > z) graph[x][y] = graph[y][x] = z;
        }
        floyd(n);
        scanf("%d%d", &s, &t);
        if (graph[s][t] < INF) cout << graph[s][t] << endl;
        else cout << "-1" << endl;
    }
    return 0;
}
//(5)SPFA
#include 
#define maxn  1000
#define INF 0x1f1f1f1f
using namespace std;

int graph[maxn][maxn],flag[maxn],dist[maxn];

void SPFA(int s, int n) {
    queue<int>q;
    memset(flag, false, sizeof(flag));
    for (int i = 0; i < n; i++) dist[i] = INF;
    q.push(s);
    dist[s] = 0;
    flag[s] = true;
    while (!q.empty()) {
        int temp = q.front();
        q.pop();
        flag[temp] = false;
        for (int i = 0; i < n; i++) {
            if (dist[i] > dist[temp] + graph[temp][i]) {
                dist[i] = dist[temp] + graph[temp][i];
                if (!flag[i]) {
                    q.push(i);
                    flag[i] = true;
                }
            }
        }
    }
}

int main() {
    int n, m, s, t;
    while (cin >> n >> m) {
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++)
                if (i == j)
                    graph[i][j] = 0;
                else
                    graph[i][j] = INF;
        for (int i = 0; i < m; i++) {
            int x, y, z;
            cin >> x >> y >> z;
            if (z < graph[x][y]) graph[x][y] = graph[y][x] = z;
        }
        cin >> s >> t;
        SPFA(s, n);
        if (dist[t] < INF) cout << dist[t] << endl;
        else cout << "-1" << endl;

    }
    return 0;
}

//(6)pair+SPFA
#include 
#define INF 0x1f1f1f1f
using namespace std;

const int maxn = 1000;
vector<pair<int, int> >Edge[maxn];

int n, m,dist[maxn];
bool flag[maxn];

void init() {
    for (int i = 0; i < maxn; i++)Edge[i].clear();
    for (int i = 0; i < maxn; i++)flag[i] = false;
    for (int i = 0; i < maxn; i++)dist[i] = INF;
}

int main() {
    int s, t;
    while (cin >> n >> m) {
        init();
        for (int i = 0; i < m; i++) {
            int x, y, z;
            cin >> x >> y >> z;
            Edge[x].push_back(make_pair(y, z));
            Edge[y].push_back(make_pair(x, z));
        }
        cin >> s >> t;
        queue<int>q;
        dist[s] = 0;
        flag[s] = true;
        q.push(s);
        while (!q.empty()) {
            int now = q.front();
            q.pop();
            flag[now] = false;
            for (int i = 0; i < Edge[now].size(); i++) {
                int v = Edge[now][i].first;
                if (dist[v] > dist[now] + Edge[now][i].second) {
                    dist[v] = dist[now] + Edge[now][i].second;
                    if (!flag[Edge[now][i].first]) {
                        q.push(Edge[now][i].first);
                        flag[Edge[now][i].first] = true;
                    }
                }
            }
        }
        if (dist[t] < INF)cout << dist[t] << endl;
        else cout << "-1" << endl;
    }
    return 0;
}

这里给出一个比较特殊的最短路题目

1.最短路的条数
2.多条时选择消防员(最短路经过的点的那些权值)多的那一条
3.记录路径

#include 
using namespace std;
const int maxn = 1e4 + 10;
const int INF = 1e9;
typedef long long LL;

struct Node{
    int v,w;
    Node(int v,int w):v(v),w(w){}
    bool operator < (const Node&rhs) const {
        return rhs.w < w;
    }
};

vector<Node>G[maxn];
bool vis[maxn];
int Pa[maxn],Weight[maxn],TotalWeight[maxn],TotalRoad[maxn];
int d[maxn];
int n,m,s,e;

void init(){
    for(int i = 0; i < maxn; i++)G[i].clear();
    for(int i = 0; i < maxn; i++)vis[i] = false;
    for(int i = 0; i < maxn; i++)d[i] = INF;
    for(int i = 0; i < maxn; i++)Pa[i] = -1; //记录路径
    for(int i = 0; i < maxn; i++)TotalRoad[i] = 0;
    for(int i = 0; i < maxn; i++)TotalWeight[i] = 0;
}

void PrintPath(int x){
    if(Pa[x] == -1) printf("%d",x);
    else {
        PrintPath(Pa[x]);
        printf(" %d",x);
    }
}

int dijkstra(int s,int e){ //传入起点终点
    priority_queue<Node>q;
    q.push(Node(s,0));
    d[s] = 0, Pa[s] = -1, TotalWeight[s] = Weight[s],TotalRoad[s] = 1;
    while(!q.empty()){
        Node now = q.top(); q.pop();
        int v = now.v;
        if(vis[v])continue;
        vis[v] = true;
        for(int i = 0; i < G[v].size(); i++){
            int v2 = G[v][i].v;
            int w = G[v][i].w;
            if(!vis[v2] && d[v2] > w+d[v]){
                d[v2] = w + d[v];
                TotalWeight[v2] = TotalWeight[v] + Weight[v2];
                Pa[v2] = v;
                TotalRoad[v2] = TotalRoad[v];
                q.push(Node(v2,d[v2]));
            }
            else if(!vis[v2] && d[v2] == w+d[v]){
                if(TotalWeight[v2] < TotalWeight[v] + Weight[v2]){ //如果消防员个数更多
                    TotalWeight[v2] = TotalWeight[v] + Weight[v2];
                    Pa[v2] = v;
                    //q.push(Node(v2,d[v2]));不需要入队了
                }
                TotalRoad[v2] += TotalRoad[v]; //加上之前的条数
            }
        }
    }
    return d[e];
}

int main(){
    //freopen("in.txt","r",stdin);
    int a,b,c;
    scanf("%d%d%d%d",&n,&m,&s,&e);
    for(int i = 0; i < n; i++)scanf("%d",&Weight[i]);
    init();
    for(int i = 0; i < m; i++){
        scanf("%d%d%d",&a,&b,&c);
        G[a].push_back(Node(b,c));
        G[b].push_back(Node(a,c));
    }
    int ans = dijkstra(s,e);
    //if(INF != ans)printf("%d\n",ans); else printf("-1\n");
    printf("%d %d\n",TotalRoad[e],TotalWeight[e]);
    PrintPath(e);
    printf("\n");
    return 0;
}

GCD和LCM

注意一下n个数的GCDLCM

//题目连接 : http://acm.hdu.edu.cn/showproblem.php?pid=1019
#include 
using namespace std;
const int maxn = 100 + 10;

int gcd(int a,int b){
    return b == 0?a:gcd(b,a%b);
}

int lcm(int a,int b){
    return a/gcd(a,b)*b;
}

int ngcd(int v[],int n){
    if(n == 1) return v[0];
    return gcd(v[n-1],ngcd(v,n-1));
}

int nlcm(int v[],int n){
    if(n == 1) return v[0];
    return lcm(v[n-1],nlcm(v,n-1));
}
int main(){
    int n,m,a[maxn];
    scanf("%d",&n);
    while(n--){
        scanf("%d",&m);
        for(int i = 0; i < m; i++)scanf("%d",&a[i]);
        //printf("%d\n",ngcd(a,m)); 
        printf("%d\n",nlcm(a,m));
    }
    return 0;
}

埃拉托斯特尼筛法

素数和分解定理等看另一篇博客总结。
比较经典的快速筛素数,给个例题,用BFS和筛素数解决

#include 
#include 
#include 
using namespace std;
const int maxn = 10000 + 10;
int prime[maxn],s,e,vis[maxn];
bool is_prime[maxn],flag;

//素数筛选的模板 :   |埃式筛法|
int Sieve(int n) {
    int k = 0;
    for(int i = 0; i <= n; i++)is_prime[i] = true;
    is_prime[0] = false,is_prime[1] = false;
    for(int i = 2; i <= n; i++) {
        if(is_prime[i]) {
            prime[k++] = i;
            for(int j = i*i; j <= n; j += i)is_prime[j] = false; // 轻剪枝,j必定是i的倍数
        }
    }
    return k;
}

struct Node {
    int v,step;
    Node(){}
    Node(int v,int step):v(v),step(step){}
};

void cut(int n,int *a){  //将各位存到a数组中
    int index = 3;
    while(n > 0){
        a[index--] = n%10;
        n /= 10;
    }
}

int rev(int *a){  //还原
    int s = 0;
    for(int i = 0; i < 4; i++)s = s*10 + a[i];
    return s;
}

void BFS(int s,int step) {
    queue<Node>q;
    Node now,next;
    q.push(Node(s,step));
    vis[s] = 1;
    while(!q.empty()) {
        now = q.front();q.pop();
        if(now.v == e) {
            flag = true;
            printf("%d\n",now.step);
            return ;
        }
        int a[4],b[4];
        cut(now.v,a);
        for(int i = 0; i < 4; i++){
            memcpy(b,a,sizeof(b));
            for(int j = 0; j <= 9; j++){
                b[i] = j;
                next.v = rev(b);
                if(next.v < 10000 && next.v > 1000 && is_prime[next.v] && !vis[next.v] && (next.v != now.v)) {
                    vis[next.v] = 1;
                    next.step = now.step+1;
                    q.push(next);
                }
            }
        }
    }
}
int main() {
    int T;
    Sieve(maxn); //先把素数筛选出来
    scanf("%d",&T);
    while(T--) {
        flag = false;
        memset(vis,0,sizeof(vis));
        scanf("%d%d",&s,&e);
        BFS(s,0);
        if(flag == 0)printf("Impossible\n");
    }
    return 0;
}

唯一分定理

ACM常用模板(+模板题)(基础)_第12张图片
具体的不细说,看一个比较简单的模板题其余还有题目在我的代码仓库。

//题目连接 : https://acm.sdut.edu.cn/onlinejudge2/index.php/Home/Index/problemdetail/pid/1634.html
#include 
using namespace std;
const int maxn = 30000;

int prime[maxn];
bool is_prime[maxn];

//素数筛选的模板 :   |埃式筛法|
int Sieve(int n) {
    int k = 0;
    for(int i = 0; i <= n; i++)is_prime[i] = true;
    is_prime[0] = false,is_prime[1] = false;
    for(int i = 2; i <= n; i++) {
        if(is_prime[i]) {
            prime[k++] = i;
            for(int j = i*i; j <= n; j += i)is_prime[j] = false; // 轻剪枝,j必定是i的倍数
        }
    }
    return k;
}

int main(){
    int T,n;
    scanf("%d",&T);
    int len = Sieve(maxn);
    while(T--){
        scanf("%d",&n);
        int ans[maxn],k = 0;
        for(int i = 0; i < len; i++){ //唯一分解定理
            while(n % prime[i] == 0){
                ans[k++] = prime[i];
                n /= prime[i];
            }
        }
        for(int i = 0; i < k; i++)printf(i == 0 ? "%d": "*%d",ans[i]);
        printf("\n");
    }
    return 0;
}

扩展欧几里得

基本算法:对于不完全为 0的非负整数 a,bgcd(a,b)表示a,b的最大公约数,必然存在整数对x,y,使得 gcd(a,b)=ax+by。,具体用处在于:

  • 求解不定方程;
  • 求解模线性方程(线性同余方程);
  • 求解模的逆元;

给出一个讲的不错的博客。
这里给出一个求解不定方程组解的测试程序:

//扩展欧几里得的学习
#include 

int exgcd(int a,int b,int &x,int &y){
    if(b == 0){
        x = 1;
        y = 0;
        return a;
    }
    int d = exgcd(b,a%b,x,y);  //d 就是gcd(a,b)
    int t = x;
    x = y;
    y = t-(a/b)*y;
    return d;
}

bool linear_equation(int a,int b,int c,int &x,int &y){
    int d = exgcd(a,b,x,y);  //d是gcd(a,b)//求出a*x+b*y = gcd(a,b)的一组解

    printf("%d*x + %d*y = %d(gcd(a,b))的一些解是\n",a,b,d);
    printf("%d %d\n",x,y); //第一组
    for(int t = 2; t < 10; t++){   //输出 a*x + b*y = gcd(a,b)的其他一些解
        int x1 = x + b/d*t;
        int y1 = y - a/d*t;
        printf("%d %d\n",x1,y1); //其余组
    }
    printf("\n\n"); //第一组


    if(c%d) return false;
    int k = c/d;    //上述解乘以 c/gcd(a,b)就是 a*x +b*y = c的解
    x *= k; y *= k;    //求得的只是其中一组解
    return true;
}

int main(){
    //freopen("in.txt","r",stdin);
    int a = 6,b = 15,c = 9;  //求解 6*x + 15*y = 9的解
    int x,y;
    int d = exgcd(a,b,x,y);

    if(!linear_equation(a,b,c,x,y))printf("无解\n");

    printf("%d*x + %d*y = %d的一些解是\n",a,b,c);
    printf("%d %d\n",x,y); //第一组
    for(int t = 2; t < 10; t++){  //输出其他的一些解
        int x1 = x + b/d*t;
        int y1 = y - a/d*t;
        printf("%d %d\n",x1,y1); //其余组
    }
    return 0;
}

再给出一个比较简单的模板题

//题目链接:http://poj.org/problem?id=1061
//题目大意:中文题,给出青蛙A,B的起点坐标,以及跳跃距离m,n以及环的长度L,求什么时候能相遇
/*解题思路: 假设跳了T次以后,青蛙1的坐标便是x+m*T,青蛙2的坐标为y+n*T。它们能够相遇的情况为(x+m*T)-(y+n*T)==P*L,其中P为某一个整数,变形一下设经过T次跳跃相遇,得到(n-m)*T-P*L==x-y 我们设a=(n-m),b=L,c=x-y,T=x,P=y.
             得到ax+by==c,直接利用欧几里得扩展定理可以得到一组x,y但是这组x,y不一定是符合条件的最优解,
             首先,当gcd(a,b)不能整除c的时候是无解的,当c能整除gcd(a,b)时,把x和y都要变为原来的c/gcd(a,b)倍,
             我们知道它的通解为x0+b/gcd(a,b)*t要保证这个解是不小于零的最小的数,
             我们先计算当x0+b/gcd(a,b)*t=0时的t值,
             此时的t记为t0=-x0/b/gcd(a,b)(整数除法),代入t0如果得到的x小于零再加上一个b/gcd(a,b)就可以了*/
#include 
using namespace std;
typedef long long LL;

LL exgcd(LL a,LL b,LL &x,LL &y){
    if(b == 0){
        x = 1;
        y = 0;
        return a;
    }
    LL d = exgcd(b,a%b,x,y);
    LL t = x;
    x = y;
    y = t-(a/b)*y;
    return d;
}

int main(){
    //freopen("in.txt","r",stdin);
    LL x,y,m,n,L,T,P;
    while(cin>>x>>y>>m>>n>>L){
        LL a,b,c;
        a = n-m;
        b = L;
        c = x-y;
        LL d = exgcd(a,b,T,P);
        if(c%d!=0){ cout<<"Impossible"<<endl; continue; }
        T = T*(c/d);
        P = P*(c/d);

        LL k = T*d/b;
        k = T-k*b/d;
        if(k < 0) k = k+b/d;
        printf("%lld\n",k);
    }
    return 0;
}

欧拉函数

欧拉函数只是工具:

  • 提供1N中与N互质的数;
  • 对于一个正整数N的素数幂分解为N = P1^q1 * P2^q2 * ... * Pn^qn,则欧拉函数 γ(N) = N*(1-1/P1) * (1-1/P2) * ... * (1-1/Pn)

这里也给出一个博客讲解。

//欧拉函数模板
//题目连接 : https://vjudge.net/contest/185827#problem/G
//题目意思 : 就是要你求1~n互素的对数(注意1~1不要重复)
#include 
#include 
const int maxn = 50000;
int phi[maxn+1], phi_psum[maxn+1];

int euler_phi(int n){
    int m = sqrt(n+0.5);
    int ans = n;
    for(int i = 2; i < m; i++)if(n % i == 0){
        ans = ans/i*(i-1);
        while(n % i == 0)n /= i;
    }
    if(n > 1)ans = ans/n*(n-1);
    return ans;
}


//筛素数的方法,求解1~n所有数的欧拉phi函数值
void phi_table(int n) {
    phi[1] = 0; //这里不计算第一个1,1和1,1是重复的,等下直接2*phi_psum[n] + 1
    for(int i = 2; i <= n; i++) if(phi[i] == 0)
        for(int j = i; j <= n; j += i) {
            if(phi[j] == 0) phi[j] = j;
            phi[j] = phi[j] / i * (i-1);
        }
}

int main() {
    int n;
    phi_table(maxn);
    phi_psum[0] = 0;
    for(int i = 1; i <= maxn; i++)phi_psum[i] = phi_psum[i-1] + phi[i];
    while(scanf("%d", &n) == 1 && n) printf("%d\n",2*phi_psum[n] + 1);
        
    return 0;
}

这里再贴上大牛的模板:

const int MAXN = 10000000;
bool check[MAXN+10];
int phi[MAXN+10];
int prime[MAXN+10];
int tot;//素数的个数

// 线性筛(同时得到欧拉函数和素表)
void phi_and_prime_table(int N) {
    memset(check,false,sizeof(check));
    phi[1] = 1;
    tot = 0;
    for(int i = 2; i <= N; i++) {
        if( !check[i] ) {
            prime[tot++] = i;
            phi[i] = i-1;
        }
        for(int j = 0; j < tot; j++) {
            if(i * prime[j] > N)break;
            check[i * prime[j]] = true;
            if( i % prime[j] == 0) {
                phi[i * prime[j]] = phi[i] * prime[j];
                break;
            } else {
                phi[i * prime[j]] = phi[i] * (prime[j] - 1);
            }
        }
    }
}

快速幂

乘法快速幂看这里。

#include 

//计算(a*b)%c
long long mul(long long a,long long b,long long mod) {
    long long res = 0;
    while(b > 0){
        if( (b&1) != 0)  // 二进制最低位是1 --> 加上 a的 2^i 倍, 快速幂是乘上a的2^i )
            res  = ( res + a) % mod;
        a = (a << 1) % mod;    // a = a * 2    a随着b中二进制位数而扩大 每次 扩大两倍。
        b >>= 1;               // b -> b/2     右移  去掉最后一位 因为当前最后一位我们用完了,
    }
    return res;
}

//幂取模函数
long long pow1(long long a,long long n,long long mod){
    long long res = 1;
    while(n > 0){
        if(n&1)
            res = (res * a)%mod;
        a = (a * a)%mod;
        n >>= 1;
    }
    return res;
}


// 计算 ret = (a^n)%mod
long long pow2(long long a,long long n,long long mod) {
    long long res = 1;
    while(n > 0) {
        if(n & 1)
            res = mul(res,a,mod);
        a = mul(a,a,mod);
        n >>= 1;
    }
    return res;
}

//递归分治法求解
int pow3(int a,int n,int Mod){
    if(n == 0)
        return 1;
    int halfRes = pow1(a,n/2,Mod);
    long long res = (long long)halfRes * halfRes % Mod;
    if(n&1)
        res = res * a % Mod;
    return (int)res;
}

int main(){
    printf("%lld\n",mul(3,4,10));
    printf("%lld\n",pow1(2,5,3));
    printf("%lld\n",pow2(2,5,3));
    printf("%d\n",pow3(2,5,3));
    return 0;
}

矩阵快速幂

这个也是比较常用的,主要还是在于常数矩阵的求解和递推公式的求解,这里给出一个博客讲解和自己写过的一道模板题,以及后面自己的总结。
ACM常用模板(+模板题)(基础)_第13张图片
转换之后,直接把问题转化为求矩阵的n-9次幂。

//题目链接:https://vjudge.net/contest/172365#problem/A
//题目大意:函数满足——If x < N f(x) = x.
//If x >= N f(x) = a0 * f(x - 1) + a1 * f(x - 2) + a2 * f(x - 3) + …… + a9 * f(x - N);
//ai(0<=i<=9) can only be 0 or 1 .
//要求f(k) % mod;
//解题思路:构造矩阵求解N*N矩阵

#include 
#include 
const int N = 10;
using namespace std;

int Mod;
struct Matrix  {
    int m[N][N];
    Matrix(){}
};

void Init(Matrix &matrix){
    for(int i = 0;i < N; i++)scanf("%d",&matrix.m[0][i]);
    for(int i = 1;i < N; i++){
        for(int j = 0;j < N; j++){
            if(i == (j+1))matrix.m[i][j] = 1;
            else matrix.m[i][j] = 0;
        }
    }
}

//矩阵相乘
Matrix Mul(Matrix &a,Matrix &b){
    Matrix c;
    for(int i = 0; i < N; i++){
        for(int j = 0;j < N; j++){
            c.m[i][j] = 0;
            for(int k = 0; k < N; k++)c.m[i][j] += a.m[i][k]*b.m[k][j];
            c.m[i][j] %= Mod;
        }
    }
    return c;
}

//矩阵幂
Matrix Pow(Matrix& matrix, int k) {
    Matrix res;
    for (int i = 0; i < N; ++i)
        for (int j = 0; j < N; ++j)
            if (i == j) res.m[i][j] = 1;
            else res.m[i][j] = 0;
    while (k) {
        if (k & 1) res = Mul(matrix,res);
        k >>= 1;
        matrix = Mul(matrix,matrix);
    }
    return res;
}

int main() {
    int k,a[N];
    while (~scanf("%d%d",&k,&Mod)) {
        Matrix matrix;
        Init(matrix);
        if (k < 10) { printf("%d\n",k % Mod); continue; }
        matrix = Pow(matrix,k-9);
        int sum = 0;
        for (int i = 0; i < N; i++)sum += matrix.m[0][i] * (9 - i) % Mod;
        printf("%d\n",sum%Mod);
    }
    return 0;
}

你可能感兴趣的:(杂七杂八,ACM)