知识点汇总
基本技巧
高精度
使用情景
当一些运算的数据大大超过了诸如
算法核心
通过对竖式运算的类模拟,进行数组中的运算。步骤大概如下:
字符串快速读入,并转换成数字
竖式运算模拟,注意进位退位
倒序输出答案数组
算法易错点
进位、左右端点维护等
代码(节选,加法和减法)
#include<bits/stdc++.h>
using namespace std;
char a[300],b[300];
int A[300],B[300];
int main()
{
scanf("%s",a+1);
scanf("%s",b+1);
int ka=strlen(a+1);
int kb=strlen(b+1);
for(int i=1;i<=ka;i++)
{
A[i]=a[ka-i+1]-'0';
}
for(int i=1;i<=ka;i++)
{
B[i]=b[kb-i+1]-'0';
}
if(ka>kb) kb=ka;
for(int i=1;i<=ka;i++)
{
A[i]+=B[i];
A[i+1]+=A[i]/10;
A[i]=A[i]%10;
}
if(A[ka+1]==1) printf("%d",A[ka+1]);
for(int i=ka;i>=1;i--) printf("%d",A[i]);
return 0;
#include<bits/stdc++.h>
using namespace std;
char a[210],b[210];
int c[210],d[210];
int main()
{
scanf("%s%s",a,b);
int la=strlen(a);
int lb=strlen(b);
for(int i=1;i<=la;i++)
{
c[i]=a[la-i]-'0';
}
for(int i=1;i<=lb;i++)
{
d[i]=b[lb-i]-'0';
}
for(int i=1;i<=la;i++)
{
c[i]-=d[i];
if(c[i]<0)
{
c[i]+=10;
c[i+1]-=1;
}
}
int t=201;
while(c[t]==0)
{
t--;
if(t==0)
{
puts("0");
return 0;
}
}
for(int i=t;i>=1;i--)
{
printf("%d",c[i]);
}
return 0;
}
经典例题
P1601 A+B Problem(高精)
P1480 A/B Problem
P1303 A*B Problem
分治算法
常见用途
用来将一个问题拆分成多个问题解决后再合并(这个是基本要求,不符合就不能用分治),时间复杂度恒为
算法关键
分治算法的关键在于识别清楚各个子任务。类似动态规划的是,分治算法同样需要保证完全包含每一种情况,否则子任务合并时会出现结果错误。
关键代码
归并排序:
void merge_sort(int l,int r)
{
if(l==r) return;
int mid=(l+r)>>1;
sorta(l,mid);
sorta(mid+1,r);
meger(l,mid,r);
return;
}
void meger(int l,int mid,int r)
{
int a1=l,a2=mid;
int b1=mid+1,b2=r,k=l-1; //对半分,由旧数组a[]更新到新数组b[]
while(a1<=a2&&b1<=b2)
{
if(a[a1]<=a[b1]) b[++k]=a[a1++];
else b[++k]=a[b1++];
} //两边的队首进行对比,直到一个数组结束
while(a1<=a2) b[++k]=a[a1++]; //清除残余,保证两边数组都被更新掉
while(b1<=b2) b[++k]=a[b1++];
for(int i=l;i<=k;i++) a[i]=b[i];
return;
}
经典例题
P2345 [USACO04OPEN] MooFest G
P1908 逆序对
P2717 寒假作业
搜索
深度优先搜索( DFS )
基础介绍
以深度为主要搜索方向,从一个点开始按某种条件搜索,直到满足一定条件后退出。顺序形如:
易错介绍
- 一定要写搜索的出口,否则容易死循环
- 对搜索的数据转移要小心(比如步数的转移),否则会答案错误
- 要保证搜索是完全的,不要少点多点
暴力部分分
对于一些时间复杂度极其苛刻的题,我们迫不得已就可以尝试暴力搜索得分,并且尽可能优化搜索
宽度优先搜索( BFS )
算法介绍
相对于深度优先搜索,宽度优先搜索会像洪水蔓延一样从起点按照路径等级差每次搜索。
具体而言,是算法先搜索距离为
算法步骤
常见情形
当我们遇到像维护回文串等层层嵌套,有一定区间特点的问题时,我们可以考虑由小区间向大区间动态规划。
做题思路
先找到动态规划的次序,确保每一次动态规划,所需要的数值已经被求出;
随后考虑循环的上限、下限以及转移方程,根据题目具体特点分析。
区间
例题
这一题就是典型的回文串问题,看似毫无头绪,实际上我们套用区间
#include<bits/stdc++.h>
using namespace std;
const int N=110,M=2010;
int n,m,c[N],d[N],mp[M],f[M][M];
char s[M];
int main()
{
scanf("%d%d",&n,&m);
scanf("%s",s+1);
for(int i=1;i<=m;i++) mp[i]=s[i]-'a'+1;
for(int i=1;i<=n;i++)
{
char nxy;
cin>>nxy;
int zdc=nxy-'a'+1;
scanf("%d%d",&c[zdc],&d[zdc]);
}
memset(f,0x3f,sizeof f);
for(int i=1;i<=m;i++) f[i][i]=0;
for(int k=1;k<=m;k++)
{
for(int i=1;i+k<=m;i++)
{
int j=i+k;
f[i][j]=min(f[i][j-1]+min(c[mp[j]],d[mp[j]]),f[i+1][j]+min(c[mp[i]],d[mp[i]]));
if(mp[i]==mp[j])
{
if(j-i==1) f[i][j]=0;
else f[i][j]=min(f[i][j],f[i+1][j-1]);
}
}
}
printf("%d",f[1][m]);
return 0;
}
普通动态规划
常见步骤
观察数据范围与时间上限
queue
基础介绍
基于系统栈的数据结构,个人感觉其实手搓栈和这个没有太多区别,但是使用
基础介绍
基于红黑树的数据结构,可以建立任意两个数据类型之间的映射,大大便利了我们对某些特殊数据的查询,能够很好地优化空间复杂度。
实际上
模板代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10; //内存复杂度依照具体题目改变
int to[N],val[N],nxt[N],head[N],tot;
int dis[N];
bool in_q[N]; //判断是否在队列q中
void add(int x,int y,int z)
{
to[++tot]=y;
val[tot]=z;
nxt[tot]=head[x];
head[x]=tot;
}
// 以上是常规的链式前向星建边
void spfa(int x) //以x为起点的最短路
{
dis[x]=0;
queue<int>q;
q.push(x);
in_q[x]=true;
while(!q.empty())
{
int u=q.front();q.pop();
in_q[u]=false;
for(int i=head[u];i;i=nxt[i])
{
int j=to[i],w=val[i];
if(dis[j]>dis[u]+w)
{
dis[j]=dis[u]+w;
if(!in_q[j]) in_q[j]=true,q.push(j);
}
}
}
}
signed main()
{
int n,m;
//自行读入
//接下来是对spfa和链式前向星的初始化
for(int i=1;i<=N;i++)
{
nxt[i]=-1;
head[i]=-1;
in_q[i]=false;
dis[i]=1e9;
}
for(int i=1;i<=m;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
//如果是无向边别建错了
}
return 0;
}
主要就是一个宽度优先搜索的思路,不断地利用队列先进先出的特点,对已经加入的点进行有规律的松弛,从而做到求出单源最短路径
Dijkstra
模板代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10, INF=0x3f3f3f3f;
int head[N], nxt[N], to[N], val[N], tot;
int dis[N]; // 存储最短距离,替代原ans数组
bool vis[N]; // 标记是否已确定最短路径
// 链式前向星加边
void add(int x, int y, int z) {
to[++tot] = y;
val[tot] = z;
nxt[tot] = head[x];
head[x] = tot;
}
// 优先队列节点(距离+节点编号)
struct Node {
int dis, id;
// 重载小于号,使优先队列成为小根堆(距离小的先出队)
bool operator<(const Node& x) const {
return x.dis < dis;
}
};
// Dijkstra算法实现(x为起点)
void dijkstra(int x) {
priority_queue<Node> q;
dis[x] = 0; // 起点距离为0
q.push({0, x}); // 直接构造节点,省略make_node
while (!q.empty()) {
Node cur = q.top(); // 取堆顶
q.pop(); // 弹出堆顶(拆分语句,修正语法错误)
int u = cur.id, d = cur.dis;
if (vis[u]) continue; // 已确定最短路径,跳过
vis[u] = true;
// 遍历邻边
for (int i = head[u]; i; i = nxt[i]) {
int v = to[i], w = val[i];
if (!vis[v] && dis[v] > d + w) { // 未确定且可松弛
dis[v] = d + w;
q.push({dis[v], v}); // 直接构造节点入队
}
}
}
}
int main() {
int n, m, s; // s为起点
scanf("%d%d%d", &n, &m, &s); // 补全输入:节点数、边数、起点
// 初始化(在读取n后进行,避免n未赋值)
tot = 0;
memset(head, 0, sizeof(head)); // 链式前向星头节点初始化为0
memset(vis, 0, sizeof(vis));
memset(dis, 0x3f, sizeof(dis)); // 距离初始化为INF
// 读入边
for (int i = 1; i <= m; i++) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
add(u, v, w);
// 若为无向图,添加 add(v, u, w);
}
dijkstra(s); // 传入起点参数
// 此处可添加输出逻辑,例如输出各点到起点的最短距离
return 0;
}