自学算法竞赛笔记

一、动态规划——从集合角度考虑DP问题

1.1 数字三角形模型

        题目: 洛谷P1216 

        思路1:记忆化搜索+DFS,从下到上累加和可以重复使用

int n=4;
int a[9][9]={{1},
            {4,6},
            {8,3,9},
            {5,7,2,1}};
//记忆化搜索+DFS 时间复杂度o(n^2) 递归算法 
int f[9][9]//记录从下到上的累加和
int dfs(int x,int y){
    if(f[x][y]!=0) return f[x][y];//记忆化搜索  当前f有值 则直接返回 不再重复搜索 
    if(x==n-1) f[x][y]=a[x][y];//到底,直接赋值 
    else f[x][y]=a[x][y]+max(dfs(x+1,y),dfs(x+1,y+1));
    return f[x][y];
    }
}

        思路2:DP 状态转移方程:a[x][y]+=max(a[x+1][y],a[x+1][y+1]) 备份数组+当前点下行方向数组

//DP 递推算法 时间复杂度0(n^2) 状态转移方程:a[x][y]+=max(a[x+1][y],a[x+1][y+1]) 
int b[9][9];//备份数组 
int p[9][9];//记录下一步的方向 
int main(){
	int x,y;
	//备份数组
	for(x=0;x<n;x++){
		for(y=0;y<n;y++){
			b[x][y]=a[x][y];
		}
	}
	for(x=n-2;x>=0;x--){
		for(y=0;y<=n;y++){
			if(a[x+1][y]>a[x+1][y+1]){
				a[x][y]+=a[x+1][y];
				p[x][y]=0;//向下 
			}
			else{
				a[x][y]+=a[x+1][y+1];
				p[x][y]=1;//向右下 
			}
		}
	}
	cout<<"max="<<a[0][0]<<endl;
	for(x=0,y=0;x<n-1;x++){
		cout<<b[x][y]<<"->";
		y+=p[x][y];//二维数组存储当前点的下一步方向 妙极了!!!! y存储当前列数 
	}
	cout<<b[n-1][y]<<endl;
}

1.2 最长上升子序列模型

//AcWing 1017. 怪盗基德的滑翔翼
#include<bits/stdc++.h>
using namespace std;

//替换顺序
void rev(int* a,int len){
	for(int i=0;i<len/2;i++){
		int tmp;
		tmp=a[i];
		a[i]=a[len-i-1];
		a[len-i-1]=tmp;
	}
} 

//解法1 时间复杂度o(n^2) DP
int f[105],a[105],k,n;
int main(){
	cin>>k;
	while(k--){
		memset(a,0,sizeof(a));
		fill(f,f+105,1);
		int ans=1;
		cin>>n;
//		for(int i=0;i<n;i++){
//			printf("%d ",f[i]);
//		}
		for(int i=0;i<n;i++){
			cin>>a[i];
		}
		for(int i=0;i<n;i++){
			for(int j=0;j<i;j++){
				if(a[i]>a[j]){
					f[i]=max(f[j]+1,f[i]);
				}
			}
			ans=max(ans,f[i]);
		}
		rev(a,n);//reverse
		for(int i=0;i<n;i++){
			for(int j=0;j<i;j++){
				if(a[i]>a[j]) f[i]=max(f[j]+1,f[i]);
			}
			ans=max(ans,f[i]);
		}
		printf("%d\n",ans);
	}
} 
//解法2 二分查找+动态更新b数组
int b[105], a[105], k, n, len;

// 二分查找
int find(int x) {
    int L = 1, R = len, mid;
    while (L <= R) {
        mid = (L + R) / 2;
        if (x > b[mid]) L = mid + 1;
        else R = mid - 1;
    }
    return L;
}

int main() {
    cin >> k;
    while (k--) {
        memset(a, 0, sizeof(a));
        memset(b, 0, sizeof(b));

        cin >> n;
        for (int i = 0; i < n; i++) {
            cin >> a[i];
        }

        // 正向计算LIS
        len = 1;
        b[1] = a[0];
        for (int i = 1; i < n; i++) {
            if (a[i] > b[len]) {
                b[++len] = a[i];
            } else {
                b[find(a[i])] = a[i];
            }
        }
        int ans = len;

        // 反转数组后再计算LIS
        reverse(a, a + n);
        memset(b, 0, sizeof(b));
        
        len = 1;
        b[1] = a[0];
        for (int i = 1; i < n; i++) {
            if (a[i] > b[len]) {
                b[++len] = a[i];
            } else {
                b[find(a[i])] = a[i];
            }
        }
        ans = max(ans, len);

        cout << ans << endl;
    }
    return 0;
}


1.3 背包模型

思路:

确定状态变量->状态转移方程->确定边界条件

(1)01背包

思路:每次打算放一个东西时,首先要考虑它放不放得下,放不下的话就直接不放;放得下的话,就要看放他划算还是不放它划算。

在具体代码实现过程中,列二维表格,定义二维数组f、一维数组w、一维数组c,f[i][j]表示前i个物品选择放入容量为j的背包后,背包中的最大价值,w[i]表示第i个物品的重量,c[i]表示第i个物品的价值。对二维数组f进行遍历赋值,判断条件如下:

第一种情况,当前i,j无法在背包中放下第i个物品,f[i][j]=f[i-1][j],继承j容量背包下前i-1个物品的最大价值。

第二种情况,当前i,j可以在背包中放下第i个物品,则需比较f[i-1][j](当前背包容量不放i的最大价值)与f[i-1][j-w[i]]+c[i](当前背包放i导致背包容量减小至j-w[i]时,减小容量情况下背包不放i只放到i-1的最大价值)。若前者大于后者,则不放i,f[i][j]=f[i-1][j];反之,则放i,f[i][j]=f[i-1][j-w[i]]+c[i]。即f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+c[i])

最终遍历完成后,f[a][b]即为背包容量为b时,选择前a件物品中任意件放入背包中的最大价值。

//AcWing 423. 采药
#include<bits/stdc++.h>
using namespace std;

int t,m,f[101][1001],c[101],w[101];
int main(){
	cin>>t>>m;
	for(int i=1;i<=m;i++){
		cin>>w[i]>>c[i];
	}
	for(int i=1;i<=m;i++){
		for(int j=1;j<=t;j++){
			if(w[i]>j) f[i][j]=f[i-1][j];
			else{
				f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+c[i]);
			}
		}
	}
	printf("%d",f[m][t]);
	return 0;
}

(2)01背包变形

有i个工作,ti表示工作耗时,di表示工作截止时间,pi表示工作价值,求给定时间内的最大工作价值 工作不能同时进行 开始了就不能被打断直到完成

输入形式: 第一行 n m 表示n组数据 限时m; 后续n组 每组m行 ti di pi 表示第i个工作的相关信息

输出形式:n行 每行表示该组数据的最大工作价值

#include<bits/stdc++.h>
 
using namespace std;
 
using i64 = long long;
 
struct node{
    int t,d,p;
};
 
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
 
    int t;
    cin >> t;
 
    while (t--) {
 
        int n;
        cin >> n;
 
        vector<node> a(n);
        for(auto &i : a)
            cin >> i.t >> i.d >> i.p;
 
        sort(a.begin(),a.end(),[](node x,node y){
            return x.d < y.d;
        });
 
        const int N = 5000;
        vector<i64> dp(N + 10,INT_MIN);
 
        dp[0] = 0;
        for(int i = 0;i < n;i ++){
            for(int j = a[i].d;j>=a[i].t;j--)
                dp[j] = max(dp[j],dp[j-a[i].t]+a[i].p);
        }
 
        i64 ans = 0;
        for(int i = 0;i <= N;i ++)
            ans = max(ans,dp[i]);
 
        cout << ans << '\n';
 
    }   
 
    return 0;
}
 


1.4 状态机模型
1.5 状态压缩DP
1.6 区间DP
1.7 树形DP
1.8 数位DP
1.9 单调队列优化的DP问题
1.10 斜率优化的DP问题


二、搜索

2.1 BFS
2.1.1 Flood Fill
2.1.2 最短路模型
2.1.3 多源BFS
2.1.4 最小步数模型
2.1.5 双端队列广搜
2.1.6 双向广搜
2.1.7 A*
2.2 DFS
2.2.1 连通性模型
2.2.2 搜索顺序
2.2.3 剪枝与优化
2.2.4 迭代加深
2.2.5 双向DFS
2.2.6 IDA*


三、图论

3.1.1 单源最短路的建图方式
3.1.2 单源最短路的综合应用
3.1.3 单源最短路的扩展应用
3.2 floyd算法及其变形
3.3.1 最小生成树的典型应用
3.3.2 最小生成树的扩展应用
3.4 SPFA求负环
3.5 差分约束
3.6 最近公共祖先
3.7 有向图的强连通分量
3.8 无向图的双连通分量
3.9 二分图
3.10 欧拉回路和欧拉路径
3.11 拓扑排序


四、高级数据结构

4.1 并查集
4.2 树状数组
4.3.1 线段树(一)
4.3.2 线段树(二)
4.4 可持久化数据结构
4.5 平衡树——Treap
4.6 AC自动机


五、数学知识

5.1 筛质数
5.2 分解质因数
5.3 快速幂
5.4 约数个数
5.5 欧拉函数
5.6 同余
5.7 矩阵乘法
5.8 组合计数
5.9 高斯消元
5.10 容斥原理
5.11 概率与数学期望
5.12 博弈论


六、基础算法

6.1 位运算
6.2 递归
6.3 前缀和与差分
6.4 二分
6.5 排序
6.6 RMQ

七、字符串处理相关内容

输入带空格字符串: getline or cin.getline

#include<stdio.h>
int main()
{
	char str[25] = { 0 };
	cin.getline(str, 25);
	printf("%s\n", str);
	return 0;
}
#include<bits/stdc++.h>
using namespace std;

int main(){
	string cmp;
	string title;
	getline(cin,title);
	cout<<title;
	return 0;
}

字符串的前缀:符号串左部的任意子串(或者说是字符串的任意首部)

字符串的后缀:符号串右部的任意子串(或者说是字符串的任意尾部)

字符串查找子串函数:

string 类有一些查找子串和字符的成员函数,它们的返回值都是子串或字符在 string 对象字符串中的位置(即下标)。如果查不到,则返回 string::npos。string: :npos 是在 string 类中定义的一个静态常量。这些函数如下:

find:从前往后查找子串或字符出现的位置。

    if (b.find(a)==string::npos){
        cout<<-1<<endl;
    }

如果找不到 会返回 string::npos


rfind:从后往前查找子串或字符出现的位置。
find_first_of:从前往后查找何处出现另一个字符串中包含的字符。例如:
s1.find_first_of("abc");  //查找s1中第一次出现"abc"中任一字符的位置
find_last_of:从后往前查找何处出现另一个字符串中包含的字符。
find_first_not_of:从前往后查找何处出现另一个字符串中没有包含的字符。
find_last_not_of:从后往前查找何处出现另一个字符串中没有包含的字符。 

string.substr(size_t pos,size_t len):从str中拷贝一部分形成新字符串,pos为起始位置,len为子串长度 省略len变量输入 可以从pos位置开始到末尾 用于提取字符串后缀

tolower:把大写字符转小写字符

八、STL

8.1 map

字典 键值对存数据

map<数据类型, 数据类型> 对象名; //初始化map对象

map[键名]=值 //新增数据或修改数据

map.insert(pair<数据类型, 数据类型>(键, 值)) //插入数据

遍历数据

map<string,string>::iterator it;
map<string,string>::iterator itEnd;
it=nameref.begin();
itEnd=nameref.end();
while(it!=itEnd) {}

九、BFS DFS

9.1 BFS

(1)板子题 P1443 马的遍历

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

queue<pair<int,int> > q;//初始化队列
const int dx[8]={-1,-2,-2,-1,1,2,2,1};
const int dy[8]={2,1,-1,-2,2,1,-1,-2};//8个方向
bool vis[500][500]; //存储是否访问
int f[500][500]; //存步数
int n,m,x,y;


int main(){
    memset(vis,0,sizeof(vis));//初始化
    cin>>n>>m>>x>>y;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            f[i][j]=-1;
        }
    }
    vis[x][y]=true;f[x][y]=0;
    q.push(make_pair(x,y));
    while(!q.empty()){
        int xx=q.front().first,yy=q.front().second;
        q.pop();
        for(int i=0;i<8;i++){
            int u=xx+dx[i],v=yy+dy[i];//从当前节点向外扩展
            if(u<1||u>n||v<1||v>m) continue;//出界
            if(vis[u][v]) continue;//已经被访问过了
			vis[u][v]=true;
            q.push(make_pair(u,v));//子节点加入PT表
            f[u][v]=f[xx][yy]+1;//对于计数问题 直接用父结点递推
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(j!=m) cout<<f[i][j]<<"\t";
            else cout<<f[i][j]<<endl;
        }
    }
    return 0;
}

注:一定要注意边界条件!!!!!!!!!!!!!循环的终止条件不是样例的行列数!!!!!!!!!!!!!!!!!!是输入变量!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

(2)二分+BFS

/*
*/
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define int ll
typedef  pair<int,int> pi ;
#define if1(x) for(int i =1 ;i<=x;i++)
#define if0(x) for(int i = 0;i<x;i++)
#define jf0(x) for(int j = 0;j<x;j++)
#define jf1(x) for(int j = 1;j<=x;j++)
#define pb push_back
const int mod = 1e9+7;
const int inf = 0x3f3f3f3f;
const int N = 500+10;
int mp[N][N];
bool vis[N][N];
int m,n;
int x[4] = {1,0,-1,0},y[4] = {0,1,0,-1};
bool check(int u)
{
    if(mp[1][1] > u) return 0;
    queue<pi> mq;
    if1(n)jf1(n) vis[i][j] = 0;
    vis[1][1] = 1;
    mq.push({1,1});
    while(!mq.empty()){
        auto [a,b] = mq.front();
        mq.pop();
        if0(4){
            int aa = a+x[i];
            int bb = b +y[i];
            if(aa>0&&bb>0&&aa<=n&&bb<=n&&!vis[aa][bb]&&mp[aa][bb]<=u){
                mq.push({aa,bb});
                vis[aa][bb] = 1;
                if(aa ==n&&bb == n)return 1;
            }
        }
        
    }
    return 0;

}
void solve(){
    cin>>n;
    if1(n)jf1(n)cin>>mp[i][j];
    int l = 1,r = 1e9+10;
    while(l<r){
        int mid = l+r>>1;
        if(check(mid)){
            r =mid;
        }
        else l = mid+1;
    }
    cout<<l<<endl;
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr); 
    int t=1;
    // cin>>t;
    while (t--)
    {
        solve();
    }
    
    return 0;
}

9.2 DFS

检测图中的唯一环

  • 描述: 给定一个无向图,包含n个节点和m条边。你需要判断这个图中是否存在且仅存在一个环。如果存在,请输出环的长度;如果不存在,输出环的数量。

  • 输入

    • 第一行包含一个整数t,表示测试数据的组数。
    • 每组测试数据的第一行包含两个整数nm,表示图的节点数和边数。
    • 接下来m行,每行包含两个整数uv,表示一条无向边连接节点uv
  • 输出

    • 对于每组测试数据,如果存在且仅存在一个环,输出Yes和环的长度。
    • 否则输出No和环的数量。

DFS 遍历图 找到环计数 最后判断计数是否等于1

#include<bits/stdc++.h>
 
using namespace std;
 
using i64 = long long;
 
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
 
    int t;
    cin >> t;
 
    while (t--) {
 
        int n, m;
        cin >> n >> m;
 
        vector g(n + 1, vector<int>());
        for (int i = 0; i < m; i ++) {
            int u, v;
            cin >> u >> v;
            g[u].push_back(v);
            g[v].push_back(u);
        }
 
        int ans = 0, cnt = 0, huan = 0;
        vector<int> vis(n + 1);
        auto dfs = [&](auto & self, int u, int fa, int num)->void{
            vis[u] = num;
            for (auto v : g[u]) {
                if (v == fa) continue;
                if (vis[v]) {
                    cnt ++;
                    ans = max(ans, num - vis[v] + 1);
                } else
                    self(self, v, u, num + 1);
            }
        };
 
        for (int i = 1; i <= n; i ++) {
            cnt = 0;
            if (!vis[i]) {
                dfs(dfs, i, 0, 1);
                huan += (cnt / 2 == 1);
            }
        }
 
        if (huan == 1 && n > 2) {
            cout << "Yes" << ' ' << ans << '\n';
        } else {
            cout << "No" << ' ' << huan << '\n';
        }
 
    }
 
    return 0;
}
 

  • 26
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值