Loading

算法笔记

结构体的sort(stable_sort)排序

1. sort使用方法

  • sort(first, second, cmp):其中first是数组起始地址,second是结束地址,cmp是排序方式。在[first, second)区间内进行排序。

2.结构体内重载比较运算符

  • 由于sort()函数只能对数组进行排序,因此定义结构体数组时,需要先重载比较运算符才能使用sort().
    1. 直接使用sort(q, q + n),默认从小到大排序。
    1. 若需要从大到小排,第三个参数为greater<Person>()即大根堆。
  • 单关键字排序:
struct Person
{
    string name;
    int score;
    bool operator< (const Person& t) const
    {
        return score < t.score;
    }
    bool operator> (const Person& t) const
    {
        return score > t.score;
    }
}q[N];
  • 双关键字排序
struct Person
{
    string name;
    int id;
    int score;
    bool operator< (const Person& t) const
    {
        if (score != t.score) return score < t.score;//分数不同时按分数从小到大排序
        else return id < t.id;//分数相同时按ID从小到大排序,我们可以把id设为数据输入时后的i值,代表谁先输入
    }q[N];

sort(q, q + n);//从小到大排序
stable_sort(q, q + n, greater<Person>());//从大到小排序

3.自定cmp函数

我们可以在main函数外定义一个bool类型的cmp比较函数,进而起到跟重载小于号相同的作用。

//还是上面的题目
bool cmp (person t1, person t2) {//person是结构体排序对象,实现从小到大排序
    if (t1.score < t2.score) return true;//返回的bool是对小于号来说
    else if (t1.score > t2.score) return false;
    else (t1.score == t2.score) return t1.id < t2.id;//t1在t2之前读入的话就返回t1
}

高精度加法

当输入数据超出变量存储范围时,用数组以大端方式存储数据,Little-Endian。
大对应高地址。而端指的是存储数据的字节顺序的尾端。

//当输入数字爆LL时,将其存到vector<int>内
int add(vector<int> &A, vector<int> &B) {
    int res = 0;
    for (int i = 0, d = 0; i < A.size() || i < B.size(); i ++) {//当A和B至少有一个非空时,继续进行
        if (i < A.size()) d += A[i];//d是i对应位数字相加的和
        if (i < B.size()) d += B[i];
        d /= 10;//是否有向上进位
        res += d;//记录下进位的次数
    }
    return res;
}
int main() {
    string a, b;
    while (cin >> a >> b) {
        vector<int> A, B;
        if (a == "0" && b == "0") break;
        for (int i = a.size() - 1; i >= 0; i --) A.push_back(a[i] - '0');//大端存储
        for (int i = b.size() - 1; i >= 0; i --) B.push_back(b[i] - '0');
        int ans = add(A, B);////vector传引用就是传名字
        
        if (ans == 0) printf("No carry operation.\n");
        else if (ans == 1) printf("1 carry operation.\n");
             else printf("%d carry operations.\n", ans);
    }
    return 0;
}

日期问题

  • 首先写出日期问题经典的三个函数:
const int month[13] = {//平年12个月的天数
    0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
    };

int is_leapyear(int year){//判断该年是否为闰年
    if ((year % 4 == 0 && year % 100 !=0) || year % 400 == 0)
        return 1;//闰年二月比平年多加一天
        else return 0;
}

int get_days (int y, int m){
    if (m == 2)
        return month[m] + is_leapyear(y);
    else return month[m];
}

接着根据题目要求具体问题具体分析。

若出现TLE,我们需要对计算次数优化到 10^8 以下

  • 优化方案: 先对year操作,再对天数操作.
int main(){
    int T = 0;//共T组数据,a表示要累加的天数
    cin >> T;
    int y, m, d, a;
    while (T--){
        cin >> y >> m >> d >> a;
        if (m == 2 && d == 29) {//特判2.29号,因为第二年一定没有2.29
            a --;
            m = 3;
            d = 1;
        }
        while (a > get_year_days(y, m)){//直接减去一整年的天数,year加1
            a -= get_year_days(y, m);
            y ++;
        }
        while (a --){//常规操作
            d ++;
            if (d > get_days(y, m)){
                m ++;
                d = 1;
                if (m > 12){
                    m = 1;
                    y ++;
                }
            }
        }
        printf ("%04d-%02d-%02d\n", y, m, d);
    }
    return 0;
}
int get_year_days (int y, int m){
    if (m <=2){//1.1至2.28加一年要根据本年是否为闰年
        return 365 + is_leapyear(y);
    }
    else if (m > 2){//3.1至12.31加一年要根据第二年是否是闰年
        return 365 + is_leapyear(y + 1);
    }
}

表达式求值和中缀转后缀

表达式求值

我们需要维护两个栈,分别是运算符栈和数栈
代码背过

#include <iostream>
#include <string>
#include <algorithm>
#include <unordered_map>
#include <stack>

using namespace std;

stack<char> op;
stack<int> num;

void eval()
{
    auto b = num.top(); num.pop();//实现a op b,需要先弹出b,在弹出a.
    auto a = num.top(); num.pop();
    auto c = op.top(); op.pop();

    int x;
    if (c == '+') x = a + b;
    else if (c == '-') x = a - b;
    else if (c == '*') x = a * b;
    else x = a / b;
    num.push(x);//运算结果压栈
}

int main()
{
    string s;
    cin >> s;

    unordered_map<char, int> pr{{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}};//定义运算符优先级
    
    for (int i = 0; i < s.size(); i ++ )
    {
        if (isdigit(s[i]))//判断是否为0~9的整数
        {
            int j = i, x = 0;
            while (j < s.size() && isdigit(s[j]))//算出该数字是多少
                x = x * 10 + s[j ++ ] - '0';
            num.push(x);
            i = j - 1;//j多加了1,需要减去
        }
        else if (s[i] == '(') op.push(s[i]);//左括号直接压op栈
        else if (s[i] == ')')//读入右括号,op栈pop至左括号
        {
            while (op.top() != '(') eval();
            op.pop();
        }
        else//读入四则运算操作符
        {
            while (!op.empty() && op.top() != '(' && pr[op.top()] >= pr[s[i]])//结束条件:op栈是否读完;是否读至左括号(括号内自成一);op栈顶优先级是否高于低于读入操作符
                eval();
            op.push(s[i]);
        }
    }

    while (!op.empty()) eval();//op栈未pop完,继续操作。
    cout << num.top() << endl;

    return 0;
}



哈弗曼树与堆

1.构建大根堆

#include <queue>
priority_queue<int> Big_heap;//大根堆
priority_queue<int, vector<int>, greater<int>> Small_heap;//小根堆

2.每次pop出两个堆顶元素,操作后在push,直到堆内仅剩一个元素

此时堆内唯一元素的值就是hafuman树的根节点数值。

while (heap.size() > 1){
	int a = heap.top();
    heap.pop();
    int b = heap.top();
    heap.pop();
    int x = a + b;
    heap.push(x);
}

3.K叉hafuman树

要点:

  1. 堆内元素双关键字排序,先按数值大小排序,再按树高height排序。(pair自动实现双关键字排序)
  2. (n - 1) % (k - 1) == 0若不满足则向堆内push{0, 0},直至满足条件(此时才能正确构建出hafuman树)
  3. 每次连续读出K个结点,sum为结点数值和,heightmax(K个结点树高)。每轮读出K个结点后,再push(sum, height + 1)
int main(){
	priority_queue<PLI, vector<PLI>, greater<PLI>> heap;//构建PLI的小根堆
    for (int i = 0; i < n; i ++){//输入pair类型元素{LL, int}
        LL x = 0;
        cin >> x;
        heap.push({x, 0});
    }
    
    while ((n - 1) % (k - 1) != 0){//补0
        heap.push({0, 0});
        n ++;
    }
    
    LL res = 0;//
    while (heap.size() > 1){
        LL sum = 0;
        int height = 0;
        for (int i = 0; i < k; i ++){
            auto x = heap.top();
            sum += x.first;
            height = max(height, x.second);//每次更新树高,共更新k次
            heap.pop();
        }
        res += sum;
        heap.push({sum, height + 1});//将新K叉树push进堆
        auto x = heap.top();//堆内唯一元素的值即是我们所求
        cout << res << endl << x.second;
    }
}

pair与双关键字排序

1.pair的创建与初始化

pair<string, string> anon;       
// 创建一个空对象anon,两个元素类型都是string
pair<string, int> word_count;    
// 创建一个空对象 word_count, 两个元素类型分别是string和int类型
pair<string, vector<int> > line; 
// 创建一个空对象line,两个元素类型分别是string和vector类型

2.pair的排序用法

//可以按如下代码自行尝试理解
#include<cstdio>
#include<map>
#include <algorithm>
using namespace std;
typedef pair<int,int> P;
 
P a[10];
 
int main(){
    for(int i=0;i<5;i++){
        scanf("%d%d",&a[i].first,&a[i].second);
    }
    sort(a,a+5);
    for(int i=0;i<5;i++){
        printf("%d    %d\n",a[i].first,a[i].second);
    }
}

还可以自定义排序方法,类似于struct,不再赘叙。


dfs递归回溯算法

1.全排列问题

分层枚举n个位置

int main(){
    cin >> n;//对1~n数字全排列
    dfs(0);
    return 0;
}

void dfs(int u){
    if (u == n){//最后一层已枚举完,输出该分支对应全排列即可。
        for (int i = 0; i < n; i ++){
            cout << path[i] << " ";
        }
    }
    else if (u < n){//u从0开始计数,此时枚举第(u + 1)层,
        for (int i = 1; i <= n; i ++){//for循环找出未使用过的数字,st[i]==1代表已用过。
            if (!st[i]) {
                st[i] = 1;
                path[u] = i;
                dfs(u + 1);//递归到下一层
                st[i] = 0;//递归返回时恢复现场
            }
        } 
    }
}
  • 要点:

1.dfs递归函数后紧跟恢复现场
2.分清楚枚举第i层(第i个位置)与第i个数字
3.不理解就画出树形递归图(u为0时代表第一个位置上的数字)

2.N皇后问题

1.第一种解法:按g[x][y]枚举各个位置的情况,分为放皇后或者不放皇后

#include <bits/stdc++.h>
using namespace std;

const int N = 10;
int col[N], row[N], d[2*N], ud[2*N];
int n;
char g[N][N];

void dfs(int x, int y, int s) {
	if (y == n) {
		y = 0;
		x ++;
	}
	if (x == n) {
		if (s == n) {
			for (int i = 0; i < n; i ++)
				puts(g[i]);
			puts("");
		}
		return;
	}
		
	if (!row[x] && !col[y] && !d[x - y + n] && !ud[y + x]) {
		
			row[x] = col[y] = d[x - y + n] = ud[y + x] = 1;
			g[x][y] = 'Q';
			dfs (x, y + 1, s + 1);
			g[x][y] = '.';
			row[x] = col[y] = d[x - y + n] = ud[y + x] = 0;	
	}
	dfs (x, y + 1, s);
}

int main() {
	cin >> n;
	for (int i = 0; i < n; i ++)
		for (int j = 0; j < n; j ++)
			g[i][j] = '.';
			
	dfs (0, 0, 0);
	return 0;
} 
  • 要点:
  • 1.dfs()返回条件是什么?--数组行数越界。注意:此时无论放置皇后个数是否为 n ,只要数组行数越界就需要返回。
  • 何时输出正确分支答案? --放置皇后个数等于题目要求的n数
  • 2.递归到下一位置的情况有几种?--画出递归图分析,放皇后或不放皇后。

2.第二种解法,按行枚举,该行皇后所在的位置

  • 与第一种解法相比,不用显式的判断x行的每一列,而用一个for循环代替,遍历该行所有位置。
#include <bits/stdc++.h>
using namespace std;
const int N = 10;
int row[N], dg[N * 2], udg[N * 2];
char g[N][N];
int n;

void dfs(int u, int s) {//u是列号
	if (u == n) {
		if (s == n) {
			for (int i = 0; i < n; i ++)
				puts(g[i]);
			puts("");
		}
		return;
	}
	for (int i = 0; i < n; i ++) {//遍历u列下的所有行i
		if (!row[i] && !dg[i + u] && !udg[i - u + n]) {
			if (g[i][u] != 'Q') {
				row[i] = dg[i + u] = udg[i - u + n] = 1;
				g[i][u] = 'Q';
				dfs(u + 1, s + 1);
				g[i][u] = '.';
				row[i] = dg[i + u] = udg[i - u + n] = 0;
			}
		}
	}


}

int main() {
	cin >> n;
	for (int i = 0; i < n; i ++)
		for (int j = 0; j < n; j ++)
			g[i][j] = '.';

	dfs(0, 0);
	return 0;

}

3.计算冲突皇后个数

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 100010;
ll col[N], row[N], udg[N * 2], dg[N * 2];

int main() {
	int n;
	cin >> n;
	while (n --) {
		int x, y;
		cin >> x >> y;
		col[y] ++;
		row[x] ++;
		udg[y - x + N] ++;
		dg[x + y] ++;
	}
	
	ll cnt = 0;
	for (int i = 0; i <= N; i ++) {
		if (col[i] >= 2) cnt += col[i] * (col[i] - 1) / 2;//组合数计算该行or列的冲突个数
		if (row[i] >= 2) cnt += row[i] * (row[i] - 1) / 2;
	}
	for (int i = 1; i <= N * 2; i ++) {
		if (dg[i] >= 2) cnt += dg[i] * (dg[i] - 1) / 2;
		if (udg[i] >= 2) cnt += udg[i] * (udg[i] - 1) / 2;
 	}
	cout << cnt;
	return 0; 
}

BFS算法

1.BFS求最短路(二维)

  • 由于bfs宽搜的特性,第一次访问到目的结点时的访问次数就是最短路径。
  • 二维bfs需要操作图g[x][y]
  • 故需要用二维数组path[][]存储dist信息,queue<pair<int int>> q 维护bfs队列。
int g[N][N], path[N][N];
int bfs(){
	queue<PII> q;
    q.push({0, 0});//初始化访问队列
	while (q.size()) {//队列非空时继续操作
    	auto t = q.front();//保存队头元素
        q.pop();
        for (int i = 0; i < 4; i ++) {//顺时针判断前后左右四个方向是否入队
            int x = t.first + dx[i], y = t.second + dy[i];
            if (x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && path[x][y] == -1) {
                q.push({x, y});//g[][]==0,表示可以通过;path[][]==-1表示该点没有访问过
                path[x][y] = path[t.first][t.second] + 1;//更新path在,大小为上一层path加一
            }
        }
    }
    return path[目标位置];//此时返回到达目标位置需要的步数
}

2.BFS求最短路(一维)

  • 一维bfs需要两种数据结构,假设操作字符串数组,那么我们需要

  • unordered_map<string, int> dist用来记录是否访问过这个字符串;同时可以作为记录当前节点的bfs层次

  • queue<string> q 作为我们的bfs队列 。

int bfs(string s){
    unordered_map <string, int> dist;//记录当前string字符串是bfs的第几层,并同时作为判重st
    queue <string> q;
    q.push(s);
    dist[s] = 0;//初始化strat字符串是第0层
    
    while (q.size()) {//队列不空时一直操作
        auto t = q.front();
        q.pop();
        if (t.find("2012") < n) return dist[t];
        //t.find(S)函数,寻找当前字符串t中是否含S字符子串,若有返回子串位置,没有找到则返回无穷大数
        for (int i = 1; i < n; i ++) {//操作字符串t的n - 1种移位
            string r = t;
            swap(r[i - 1], r[i]);
            if (!dist.count(r)) {//是否访问过字符串r,没有则入队,且更新到r的dist
                q.push(r);
                dist[r] = dist[t] + 1;//更新dist
            }
        }
    }
    return -1;//没找到“2012”,返回-1 
}

模拟题

  1. 在进入每轮循环时先判断退出条件
  2. 可以利用数学关系简化模拟流程


因式分解

1.分解质因数:

  • 给定数字N,将其分解为质因数,并输出每个质因数的底数指数
  • 此处有两个数学结论,
    1. N最多有一个大于sqrt(N)的质因数,因此我们只需要找到其小于等于sqrt(N)的质因数,最后再判断 N 是否分解完 (N = 1 ?),若N != 1,则最后剩下的 N 也是一个质因数。
    2. i = 2开始枚举,每次除净 i,则从2 - N / i满足条件(N % i == 0)的i都是N的质因数.
solution:
#1.判定n是不是质数
int is_prime(int x){
    if (x < 2) return 0;
    for (int i = 1; i <= x / i; i ++) //从1到sqrt(n),因式分解的性质
    	if (x % i == 0) return 0;//若x能被i整除,则x不是质数
    return 1;  
}

#2.将数字n分解质因数
void divide(int n) {
	for (int i = 2; i <= n / i; i ++) {//从i = 2开始枚举所有质因数,注意i的取值范围,当n为平方数时,i必须取到他的开方大小,也就是 i^2 = n;因此必须是<=号
        if(n % i == 0) {//(易证)满足此条件的i只能是质数
            int cnt = 0;
           	while(n % i == 0) {//除干净所有的质数i
           		cnt ++;
                n /= i;
           	}
            printf("底数为%d 指数为%d", i, cnt);
        }
        if(n > 1) printf("底数为%d 指数为%d", n, 1);//若枚举到最后n不等于1,则最后这个数也是n的质因数
}

2.阶乘因式分解质因数

  • 要求:给定数字N,返回N!的末尾0的个数
  • 算法1:求出N!并求出末尾0的个数。时间复杂度O(N),无法通过所有数据
  • 算法2:阶乘因式分解

已知 2 * 5 == 10,故我们只要找出1~n中含质因数2的个数s2和含质因数5的个数s5,取min,就能得到含因数10的个数
观察数列1~n:1,2,3,4,5,6,7,8,9,10...
假设我们求 n!5因子的个数,即:5,10,15,20,25...
提取公因数5,有5 *(1,2,3,4,5),我们发现提出因子5后仍然满足我们的要求,故记录一个因子的个数cnt = n / 5

int main() {
	int N = 0;
    cin >> N;
    int cnt = 0;
    while (N) {
    	N /= 5;
        cnt += N;
    }
    cout << cnt;
}

3.求a和b的最大公约数(greatest common divisor)

  • 可以直接使用STL中的__gcd(int a, int b)函数
#include <iostream>
#include <algorithm>
using namespace std;

int main()
{
	cout << "gcd(6, 20) = " << __gcd(6, 20) << endl; 
}

#OUTPUT:
gcd(6, 20) = 2

位运算

1.将数字x中的每一位都取出来

  • 假设有数字x不超过32位,即unsigned int现将其二进制表示下的01串表示为字符串:
string str1;
for(int i = 31; i >= 0; i --) { //从最高位开始,每次取出x二进制表示下的一位数字
    str1 += to_string(x >> i & 1); //x >> i 表示 x 右移i位
}
  • 存在的问题:当数字不足32位时,即我们转化得到的字符串str1前面会有一连串的“前导零”
string str1;
while (x){
	str1 += to_string(x & 1);//从个位开始取出数字,直到x为零
    x >>= 1;
}
reverse(str1.begin(), str1.end());//将得到的逆序字符串翻转

vector.begin() .end(); .front() .back()的区别:
前一对是传递指针,后一组是传递引用


操作矩阵

1.矩阵乘法

  • void mul (int res[][N], int a[][N], int b[][N]) {...}二维数组传递参数,第一维可以省略,第二维不可省略(告诉编译器数组行列是如何划分的)

链接:关于二维数组传参做形参

void mul (int res[][N], int a[][N], int b[][N]) {
	int tem[N][N] = {0};//中间数组,用来存储一次矩阵乘法的结果
    for (int i = 0; i < n; i ++) 
        for (int j = 0; j < n; j ++) 
            for (int k = 0; k < n; k ++)
                tem[i][j] += a[i][k] * b[k][j];
    memcpy(res, tem, sizeof tem);//将结果复制到res数组
}
int main() {
    a[][];//将初始数组输入
    for (int i = 0; i < n; i ++) res[i][i] = 1;//单位矩阵,实现第一次矩阵乘法
    while (k --){//计算a矩阵的k次幂
        mul (res, res, a);//矩阵乘法
}

2.旋转矩阵

  • 输出矩阵a顺时针旋转90度的新矩阵
  • 初始化vectorvector<vector<int>> a(N, vector<int> (M));定义一个NM列的二维数组
  • vector存储矩阵,并传递参数给核心函数vector<vector<int>> rotate
# 将整个矩阵旋转
vector<vector <int>> rotate (vector<vector <int>> a) {//传递二维vector参数
	auto res = a;
    for (int i = 0; i < n; i ++) 
        for (int j = 0, k = n - 1; j < n; j ++, k --)//画图理解,res矩阵的第i行是a矩阵第i列倒序输出
            res[i][j] = a[k][i];
    return res;
}

int main(){
	vector<vector<int>> a(N, vector<int> (N));//用vector存储,并初始化
    for (int i = 0; i < 4; i ++) {//实现四次旋转,每次顺时针旋转90度,可以等效为逆时针旋转
    	a = rotate(a);
    }
}
# 旋转 坐标(x, y) 为左上角且 边长为n 的矩阵a
# 按旋转整个矩阵的模板写,在最后赋值的时候加上偏移量x, y即可
void rotate(int x, int y, int b) {//常规写法,普通定义二维数组
    int tem[N][N] = {0};//中间数组
    for (int i = 0; i < n; i ++)
        for (int j = 0, k = n - 1; j < n; j ++, k --)
            tem[i + x][j + y] = a[k + x][i + y];//在常规旋转的模板上加上坐标偏移量
    memcpy(a, tem, sizeof tem);//更新a数组,以实现多次旋转操作
}

3.分块矩阵

  • 从给定的n * m矩阵中找出最小面积子矩阵,使得子矩阵内元素之和不大于K
#include <bits/stdc++.h>
using namespace std;

int n, m, k;
const int N = 110;
int a[N][N], s[N][N];

int compute(int i, int j, int x, int y) {//前缀和计算左上角为(i, j) 右下角为(x, y)的矩阵元素之和。
    int ans = 0;
    ans = s[x][y] - s[i - 1][y] - s[x][j - 1] + s[i - 1][j - 1];
    return ans;
    
}

int main() {
    cin >> n >> m >> k;
    for (int i = 1; i <= n; i ++) {
        for (int j = 1; j <= m; j ++) {
            cin >> a[i][j];
            s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];//构造前缀和数组,代表以(i, j)为右下角的矩阵元素和。
        }
    }
    
    int ans = 0x3f3f3f3f;
    for (int i = 1; i <= n; i ++) {//先枚举左上角位置,再枚举右下角位置
        for (int j = 1; j <= m; j ++) {
            for (int x = i; x <= n; x ++) {
                for (int y = j; y <= m; y ++) {
                    if (compute(i, j, x, y) >= k) {//当矩阵元素和大于等于K时计算矩阵面积
                        ans = min(ans, (y - j + 1) * (x - i + 1));
                    }
                }
            }
        }
    }
    
    if (ans >= 0x3f3f3f3f) cout << -1;
    else cout << ans;
    return 0;
}

前缀和

  • s[i]代表数组a[]1 ~ i位置上的数据之和
a[0] = 0;
for (int i = 1; i <= n; i ++) {//i从1开始,a[0]初始化为0,防止越界
	s[i] = s[i - 1] + a[i];
}
  • 通常通过数学关系推出s[i] - s[j]的关系表达式,再通过前缀和求出结果。常用来求解一段的距离
#给出环形数组a[], 求i到j的最短距离,a[i] 表示i - 1到 i 的距离。
int a[N], s[N];
int main() {
    int n ;
    cin >> n;
    for (int i = 1; i <= n; i ++)
        cin >> a[i];//输入初始数组
    for (int i = 1; i <= n; i ++)
        s[i] = s[i - 1] + a[i];
    
        int i, j;
        cin >> i >> j;
        if (j < i) swap(i, j);//给出的i,j大小关系不确定
        printf("%d\n", min(s[j - 1] - s[i - 1], s[n] - s[j - 1] + s[i - 1]));
    
}

最短路问题

1.spfa

  • 建议直接使用spfa模板spfa() + add() 加边模板

    分为三个部分:

    1. 建立队列(根据情况选择 模拟循环队列 还是 使用queue),
    2. 队列非空时循环,
    3. 单次循环内执行入队出队,记得同步更新st[]状态数组
int h[N], e[M], w[M], ne[M], idx;
void add(int a, int b, int c) {//头结点为a, 边表结点为b, 边权为c
    e[idx] = b; w[idx] = c; ne[idx] = h[a]; h[a] = idx ++;//尾插法逆序建立邻接表
}

void spfa(int start, int dist[]){//朴素版,传入start起始位置和dist[]数组
    queue<int> q;
    q.push(start);
    st[start] = 1;//将start入队,同步更改st数组
    memset(dist, 0x3f, sizeof dist1);//清空距离数组,不能写dist不然返回的是指针长度

    dist[start] = 0;
    while(q.size()) {//当队列不空时,继续循环
        int t = q.front();
        q.pop();//出队
        for (int i = h[t]; i != -1; i ++) {//遍历该头结点对应的邻接表,直到遍历到i = -1为止
            if (...) continue;//判断条件,不满足条件时跳过该次更新
            int j = e[i];
            if (dist[j] > dist[t] + w[i]) {//更新距离
                dist[j] = dist[t] + w[i];
                if (! st[j]) {//当j未入队时,将其入队
                	q.push(j);
                	st[j] = 1;
            	}
            }
        }
    }
}



哈希表

1.按字典序输出结果时,可以考虑使用<map>存储键值。

//给出一个 01 字符串,求其每一个子串出现的次数,并将结果按字典序输出,有多组输入数据

int main (){
    map <string, int> mp;
    while (cin >> s) {
        mp.clear();//清空上一次输入的mp
    	for (int i = 0; i < s.size(); i ++) {//遍历s的起点
            string str;//取出以s为起点的所有01串
        	for (int j = i; j < s.size(); j ++) {
            	str += s[i];
                mp[str] ++;//对应hash值+1
        	}
        }
        for (auto t : mp) {//按字典序输出mp中所有元素
            cout << t.first << ":" << t.second << endl;
        }
    }
    return 0;
}

  1. 操作字符串时有两个小函数:isalpha()isupper()
    • isalpha(val):用来判断val是否是字母
    • isupper(val):用来判断val是否是大写字母
//统计一行英文句子中出现的各个单词出现次数。单词由空格、句号和逗号隔开,并将结果由字典序输出。

int main() {
	string s;
    getline(cin, s);
    //记住这个函数形式!
    for (int i = 0;  i < s.size(); i ++) {
        if (!isalpha(s[i])) continue;//isalpha(val):判断val是否是一个字母
        int j = i;
        string str;
        while (isalpha(s[j]) && j < s.size()) {
            if (isupper(s[j])) s[j] += 'a' - 'A';//isupper(val):判断val是否是一个大写字母
            str += s[j ++];
        }//退出while循环时,j的值多加了一次1
        mp[str] ++;//对应位置hash值+1
        i = j;//将i指针跳到j的位置上
    }
    //
    for (auto t : mp) {
        cout << t.first << ":" << t.second << endl;
    }
    return 0;
}

分形

  • 将每一次的递归结果用vector <string>来存储字符串图形
  • 对第k层来说,首先我们需要递归找到第k - 1层对应图形,并将其存储下来,和原始图形一起更新我们第k层的图形
#include <bits/stdc++.h>
using namespace std;

int n, k;
vector <string> p;//存储原始形态get(1)

vector <string> get (int k) {//输出p的k次形态
    if (k == 1) return p;//递归退出
    vector<string> s = get(k - 1);//递归
    int m = s.size();//上一层递归结果的边长,记做m
    vector<string> ans(n * m);
    for (int i = 0; i < n * m; i ++)//将ans初始化
        ans[i] = string(n * m, ' ');
    
    for (int i = 0; i < n; i ++) //get(n - 1)中是空格的位置,get(n)中相应位置的m*m格子一定也是空格。
        for (int j = 0; j < n; j ++)
            if (p[i][j] != ' ') 
                for (int x = 0; x < m; x ++)//填入非空格子
                    for (int y = 0; y < m; y ++) 
                        ans[i * m + x][j * m + y] = s[x][y];//根据上一层的边长m和原始边长n即可求出相应坐标
                    
            
    return ans;
}

int main() {
    while (cin >> n) {
        p.clear();
        getchar();//
        if (n == 0) break;
        
        for (int i = 0; i < n; i ++) {//
            string line;
            getline(cin, line);
            p.push_back(line); 
        }
        cin >> k;
        auto ans = get(k);
        for (auto s : ans)//
            cout << s << endl;
    }
    
}
posted @ 2022-03-25 21:14  王子春  阅读(38)  评论(0编辑  收藏  举报