经常打codeforce,比赛,给学生讲算法那的啥啊的都需要模板
那么也把我整理的这些基础数据结构与算法的模板分享给大家
可能有一点点小错误,可以在评论区留言哈~
/*
名称定义:
n: 数据规模 m: 表示边数等数据规模
v: vertex 图中的顶点
fa: father 一般用来指代父亲
ret: return 函数返回值
res: result 结果值
ans: answer 结果值
g: graph 存图
dis: distance 存最短路径长度
sup: support 作为辅助的数据结构
temp: temporary 临时变量/数组
visit: 是否访问过
cnt: count 次数
d: degree (入)度
pre: previous 之前的/前缀
p: position 下标
w: weight 权重
arr: array 数组
mp: map的名字
se: set的名字
op,opt: operation操作符
*/
#define N (int)1e5+10
using namespace std;
/*
O(n) BFS求无权图最短路径
虽然dfs和bfs很像
在求最短路径方面,dfs可是被bfs
甩了整整八条街哦!
*/
int dis[N];
vector<int> g[N];
void bfs(int start)
{
memset(dis, 0x3f, sizeof(dis));
dis[start] = 0;
queue<int> sup;
sup.push(start);
while (!sup.empty())
{
int front = sup.front();
sup.pop();
for (int i = 0; i < g[front].size(); i++)
{
if (dis[front] + 1 < dis[g[front][i]])
{
dis[g[front][i]] = dis[front] + 1;
sup.push(g[front][i]);
}
}
}
}
/*
O(n^2)暴力dj算法,pre数组存具体路径
*/
int g[N][N];
bool visit[N];
int dis[N], pre[N];
void dijkstra(int start)
{
memset(dis, 0x3f, sizeof(dis));
visit[start] = true; dis[start] = 0; pre[start] = start;
int i, j;
int tempv = start;
for (i = 1; i < n; i++)
{
for(j = 1; j <= n; j++)
{
if (!visit[j] && dis[tempv]+g[tempv][j] < dis[j])
{
dis[j] = dis[tempv]+g[tempv][j];
pre[j] = tempv;
}
}
int temp = 0x7fffffff;
for (i = 1; i <= n; i++)
{
if (!visit[i])
{
if (dis[i] < temp)
{
temp = dis[j];
tempv = i;
}
}
}
visit[tempv] = true;
}
}
/*
O(eloge)堆优化dj算法,在n的数量级>=1e5时必须采用这种堆优化+邻接表方式
*/
struct node{
int p, w;
node(int a, int b):p(a), w(b){}
bool operator< (const node& b) const
{
return w > b.w;
}
};
vector<node> g[N];
priority_queue<node> sup;
void dijkstra(int start)
{
memset(dis, 0x3f, sizeof(dis));
dis[start] = 0; pre[start] = start;
sup.push(node(start, 0));
while (!sup.empty())
{
node front = sup.top();
sup.pop(); int tempv = front.p;
if (visit[tempv]) continue;
visit[tempv] = true;
for (int i = 0; i < g[tempv].size(); i++)
{
int p = g[tempv][i].p;
if (!visit[p] && dis[tempv]+g[tempv][i].w < dis[p])
{
dis[p] = dis[tempv]+g[tempv][i].w;
pre[p] = tempv;
sup.push(node(p, dis[p]));
}
}
}
}
/*
O(n^2) 暴力Prim最小生成树算法
*/
int g[N][N];
int dis[N];
bool visit[N];
int prim(int start)/*起点可以任意选,因为最小生成树一定包括每一个点*/
{
int ret = 0;
memset(dis, 0x3f, sizeof(dis));
int i, j, tempv = start;
visit[tempv] = true; dis[start] = 0;
for (i = 1; i < n; i++)
{
for (j = 1; j <= n; j++)
{
if (!visit[j] && g[tempv][j] < dis[j])
{
dis[j] = g[tempv][j];
}
}
int temp = 0x3fffffff;
for (j = 1; j <= n; j++)
{
if (!visit[j] && dis[j] < temp)
{
temp = dis[j];
tempv = j;
}
}
visit[tempv] = true, ret += dis[tempv];
}
return ret;
}
/*
O(eloge) 堆优化Prim最小生成树算法
*/
vector<int> g[N];
struct node{
int p, w;
node(int a, int b):p(a), w(b){}
bool operator< (const node &b) const
{
return w > b.w;
}
};
int prim(int start)
{
int ret = 0;
memset(dis, 0x3f, sizeof(dis));
priority_queue<node> sup;
sup.push(node(start, 0));
while (!sup.empty())
{
node front = sup.front();
sup.pop(); int tempv = front.p;
if (visit[tempv]) continue;
visit[tempv] = true; ret += dis[tempv];
for (int i = 0; i < g[tempv].size(); i++)
{
int p = g[tempv][i].p;
if (!visit[p] && g[tempv][i].w < dis[p])
{
dis[p] = g[tempv][i].w;
sup.push(node(p, dis[p]));
}
}
}
return ret;
}
/*
其实从代码来看,dj算法和prim算法几乎如出一辙。
说白了呢,这两种算法都只是BFS+贪心算法而已
每次选择最优的基点然后再更新相邻点,n-1次循环后
就可以跑完整张图(起点那次不用跑,所以n-1次)
除此以外需要注意的一点是切勿盲目堆优化
一般来说ACM中的n和e是同一数量级(基本都是1e5)
所以eloge的时间复杂度远远优于n^2
但是如果是稠密图,即e = O(n^2)时
eloge = 2n^2logn > n^2
这是选择暴力的dj算法会更好一点
使用stl中的堆优化,时间复杂度eloge
自己手写二叉堆(吃饱了撑的)的话,时间复杂度是elogn
因为不用像stl中的堆一样,每次遇到一个小于当前dis的点就无脑入堆
而是通过一个id->堆中位置的一个映射表调整位置
这样就能保持堆中元素的数量始终小于n,e次跑图之后时间复杂度即为
elogn+nlogn = elogn
使用斐波那契堆时,时间复杂度为nlogn+e
但是太难写了,而且常数大,故比赛时不会用到
希望你能够抱着一颗探索知识的心,真正的吃透和弄懂这些基础算法
并对它们的时空复杂度有着明确的认识
就我个人观点来看,kruscal算法更易理解,更容易写代码和做理论题
并且还可以较容易直观地判断图的连通性
但它的时间复杂度只能是eloge
当稠密图时,用非堆优化的prim算法O(n^2)时间复杂度则更为优秀
*/
/*
O(eloge) kruskal最小生成树算法
*/
int fa[N];
struct node{
int u, v, w;
node(){}/*注意不要忘了写上无参构造函数哦,因为如果定义了其他构造函数
系统就不会自己加默认构造函数,arr数组就无法构造出来!*/
node(int a, int b, int c):u(a), v(b), w(c){}
bool operator< (const node &b) const
{
return w < b.w;
}
}arr[N];
int jtfind(int x)/*并查集----路径压缩*/
{
return fa[x] == x ? x : fa[x] = jtfind(fa[x]);
}
int main(void)
{
int n, m;
scanf("%d%d", &n, &m);
int i, j;
for (i = 1; i <= n; i++) fa[i] = i;
for (i = 1; i <= m; i++)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
arr[i] = node(a, b, c);
}
sort(arr+1, arr+1+m);
int cnt = 0, res = 0;
for (i = 1; i <= m; i++)
{
if (jtfind(arr[i].u) != jtfind(arr[i].v))
{
fa[jtfind(arr[i].u)] = jtfind(arr[i].v);
res += arr[i].w;
if (++cnt == n-1) break;
}
else continue;
}
if (cnt == n-1)
{
cout << "最小生成树的权重是" << res;
}
else
{
cout << "此图不连通呀!" << endl;
}
}
/*
O(e^2)破圈法求最小生成树
*/
struct node{
int u, v, w;
node(){}
node(int a, int b, int c):u(a), v(b), w(c){}
bool operator< (const node &b) const
{
return w > b.w;
}
}arr[N];
vector<int> g[N];
bool visit[N];
bool dfs(int start, int end)
{
if (start == end) return true;
visit[start] = true;
bool ret = false;
for (int i = 0; i < g[start].size(); i++)
{
if (!visit[g[start][i]])
ret = ret || dfs(g[start][i], end);
}
return ret;
}
int main(void)
{
int n, m;
scanf("%d%d", &n, &m);
int i, j;
int res = 0;
for (i = 1; i <= m; i++)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
arr[i] = node(a, b, c);
res += c;
g[a].push_back(b);
g[b].push_back(a);
}
sort(arr+1, arr+1+n);
int cnt = m - (n - 1);/*要去除的边数*/
for (i = 1; i <= m; i++)
{
memset(visit, false, sizeof(visit));
int u = arr[i].u, v = arr[i].v;
g[u].erase(find(g[u].begin(), g[u].end(), v));
g[v].erase(find(g[v].begin(), g[v].end(), u));
if (dfs(u, v))
{
res -= arr[i].w;
if (--cnt == 0) break;
}
else
{
g[u].push_back(v);
g[v].push_back(u);
}
}
cout << res;
}
/*
O(e)求拓扑序,有环的有向图是没有拓扑序的!
如果题目中点的下标不是1--n,可以用map或者二分去离散化
*/
vector<int> g[N];
int d[N];
vector<int> res;
queue<int> sup;
int main(void)
{
int n, m;
scanf("%d%d", &n, &m);
int i;
for (i = 1; i <= m; i++)
{
int a, b;
scanf("%d%d", &a, &b);
g[a].push_back(b);
d[b]++;
}
for (i = 1; i <= n; i++)
{
if (!d[i]) sup.push(i);
}
while (!sup.empty())
{
int front = sup.front();
sup.pop(); res.push_back(front);
for (i = 0; i < g[front].size(); i++)
{
if (--d[g[front][i]] == 0) sup.push(g[front][i]);
}
}
if (res.size() != n)
{
pritnf("这个图有环环哦~");
}
else
{
for (i = 0; i < res.size(); i++)
{
printf("%d ", res[i]);
}
printf("%d", res[i]);/*为了行末没有空格!*/
}
}
/*
O(n+m) KMP算法----求模式串在主串中的出现次数
非优化版本(反正优化了也只是个常数,一般没卵用)
*/
char s[N], p[N];/*主串和模式串*/
int next[N];
void getnext()
{
int i = 0, j = -1;
next[0] = -1;
int len = strlen(p);
while (i < len)
{
if (j == -1 || p[i] == p[j])
{
next[++i] = ++j;
}
else
{
j = next[j];
}
}
}
int cal()
{
int ret = 0;
int len1 = strlen(s), len2 = strlen(p);
int i = 0, j = 0;
while (i < len1)
{
if (j == -1 || s[i] == p[j])
{
i++, j++;
}
else
{
j = next[j];
}
if (j == len2)
{
ret++;
i--, j = next[j-1];
}
}
return ret;
}
int main(void)
{
scanf("%s%s", s, p);
getnext();
int res = cal();
}
/*
O(n)非递归二叉树前中后序遍历
*/
class tree{
private:
int data;
tree* left, *right;
public:
void pretravel(){
stack<tree*> s;
tree* p = this;
while(p || !s.empty()){
while(p){
cout << p->data << " ";
s.push(p);
p = p->left;
}
if(!s.empty()){
p = s.top();
s.pop();
p = p->right;
}
}
}
void intravel(){
tree* a[10005];
int top = -1;
tree* p = this;
while(p || top != -1){
while(p){
a[++top] = p;
p = p->left;
}
if(top != -1){
p = a[top--];
cout << p>data << " ";
p = p->right;
}
}
}
void posttravel(){
tree* p = this;
stack<tree*> s;
tree *pre = NULL, *temp = NULL;
while(p || !s.empty()){
while(p){
s.push(p);
p = p->left;
}
if(!s.empty()){
temp = s.top();
if(!temp->right || temp->right == pre){
cout << temp->data << " ";
pre = temp;
s.pop();
}
else
p = temp->right;
}
}
}
};
/*
O(n+e)求关键路径critical path
*/
struct node{
int v, w;
node(int a, int b)
{
v = a, w = b;
}
};
vector<node> g[N];
int d[N];
vector<int> tp;
queue<int> sup;
int et[N], lt[N];
int main(void)
{
int n, m;
scanf("%d%d", &n, &m);
int i, j;
for (i = 1; i <= m; i++)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
g[a].push_back(node(b, c));
d[b]++;
}
for (i = 1; i <= n; i++)
{
if (!d[i]) sup.push(i);
}
while (!sup.empty())
{
int front = sup.front();
sup.pop(); tp.push_back(front);
for (i = 0; i < g[front].size(); i++)
{
if (--d[g[front][i].v] == 0) sup.push(g[front][i].v);
}
}
if (tp.size() != n)
{
printf("这个图有环环哦~");
return 0;
}
for (i = 0; i < tp.size(); i++)
{
int u = tp[i];
for (j = 0; j < g[u].size(); j++)
{
int v = g[u][j].v, w = g[u][j].w;
et[v] = max(et[v], et[u] + w);
}
}
for (i = 1; i <= n; i++) lt[i] = et[tp[n-1]];
for (i = tp.size()-1; i >= 0; i--)
{
int u = tp[i];
for (j = 0; j < g[u].size(); j++)
{
int v = g[u][j].v, w = g[u][j].w;
lt[u] = min(lt[u], lt[v] - w);
}
}
for (i = 1; i <= n; i++)
{
for (j = 0; j < g[i].size(); j++)
{
int u = i, v = g[i][j].v, w = g[i][j].w;
if (lt[v] - et[u] == w)
{
printf("%d->%d\n", u, v);
}
}
}
}
/************************华丽的分割线************************/
/*以下为写题目专用的高级数据结构与算法*/
/*
O(nlogn)离散化模板
*/
void init(void)
{
int i, temp[N];
for (i = 1; i <= n; i++)
{
temp[i] = arr[i];
}
sort(temp+1, temp+1+n);
int m = unique(temp+1, temp+1+n) - temp;
for (i = 1; i <= n; i++)
{
arr[i] = lower_bound(temp+1, temp+m, arr[i]) - temp;
}
}
/*
树状数组 https://www.cnblogs.com/hsd-/p/6139376.html(讲的比较好)
适用范围:单点更新,区间求和(也可以区间求最值和区间修改,但是不太容易理解)
*/
inline int lowbit(int t)
{
return t&(-t);
}
int query(int x)
{
int ret=0;
for(int i = x; i; i -= lowbit(i)) ans += c[i];
return ret;
}
void update(int x, int change)
{
for(int i = x; i <= n; i += lowbit(i)) c[i] += change;
}
/*
线段树
适用范围:单点/区间修改,区间赋值,区间查询
相较于BIT,常数较大,代码量较长,适用范围更广
*/
#define lc root<<1
#define rc root<<1|1
struct seg{/*这里的val代表求和,要检索的如果是区间最大值需要相应修改代码*/
int l, r;
ll val, lazy, tag;/*lazy用于区间修改。tag用于区间赋值,tag等于-1时表示没有赋值操作*/
/*多数时候用不到lazy和tag,下面的pushdown,assignment函数就不用写了*/
}t[4*N];
void build(int root, int l, int r)
{
t[root].l = l, t[root].r = r;
t[root].lazy = 0;
t[root].tag = -1;
if (l == r)
{
scanf("%lld", &t[root].val);
return;
}
int m = (l + r) >> 1;
build(lc, l, m);
build(rc, m+1, r);
t[root].val = t[lc].val + t[rc].val;
}
inline void imptag(int root, ll change)
{
t[root].tag = change;
t[root].val = (t[root].r - t[root].l + 1) * change;
t[root].lazy = 0;
}
inline void implazy(int root, ll change)
{
t[root].lazy += change;
t[root].val += (t[root].r - t[root].l + 1) * change;
}
inline void pushdown(int root)
{
ll temp1 = t[root].tag;
if (temp1 != -1)
{
imptag(lc, temp1);
imptag(rc, temp1);
t[root].tag = -1;
}
ll temp2 = t[root].lazy;
if (temp2)
{
implazy(lc, temp2);
implazy(rc, temp2);
t[root].lazy = 0;
}
}
void assignment(int root, int l, int r, ll change)/*基本和update一样,复制一下改下名字就行*/
{
if (r < t[root].l || l > t[root].r) return;
if (l <= t[root].l && t[root].r <= r)
{
imptag(root, change);
return;
}
pushdown(root);
assignment(rc, l, r, change);
assignment(lc, l, r, change);
t[root].val = t[lc].val + t[rc].val;
}
void update(int root, int l, int r, ll change)
{
if (r < t[root].l || l > t[root].r) return;
if (l <= t[root].l && t[root].r <= r)
{
implazy(root, change);
return;
}
pushdown(root);
update(rc, l, r, change);
update(lc, l, r, change);
t[root].val = t[lc].val + t[rc].val;
}
ll query(int root, int l, int r)
{
if (r < t[root].l || l > t[root].r) return 0;
if (l <= t[root].l && t[root].r <= r)
{
return t[root].val;
}
pushdown(root);
return query(rc, l, r) + query(lc, l, r);
}
/*
O(n+e) tarjan算法
求图中所有的强联通分量
*/
int dfn[N], low[N], col[N], s[N];
bool visit[N];
int cnt, top, idx;
void tarjan(int now)
{
dfn[now] = low[now] = ++cnt;
visit[now] = true;//visit表示该点是否在栈中,而非是否没访问
s[++top] = now;
int i;
for (i = 0; i < g[now].size(); i++)
{
int temp = g[now][i];
if (!dfn[temp])
{
tarjan(temp);
low[now] = min(low[now], low[temp]);
}
else if (visit[temp])
{
low[now] = min(low[now], dfn[temp]);
}
}
if (low[now] == dfn[now])
{
idx++;
while (s[top] != now)
{
visit[s[top]] = false;
col[s[top]] = idx;
top--;
}
visit[now] = false;
col[now] = idx;
top--;
}
}
/*
O(n)马拉车算法求最长回文子串
一般来说可以用dp或是枚举中心点法做到O(n^2)就已经够了
*/
string malache(string& s)
{
vector<int> t(2); t[0] = -2; t[1] = -1;
for (int i = 0; i < s.size(); ++i)
{
t.push_back(s[i]);
t.push_back(-1);
}
vector<int> p(t.size(), 0);
int mx = 0, id = 0, resLen = 0, resCenter = 0;
for (int i = 1; i < t.size(); ++i)
{
p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
while (t[i + p[i]] == t[i - p[i]]) ++p[i];
if (mx < i + p[i])
{
mx = i + p[i];
id = i;
}
if (resLen < p[i])
{
resLen = p[i];
resCenter = i;
}
}
return s.substr((resCenter - resLen) / 2, resLen - 1);
}
/*
O(n)算法求lca
p和q在调用函数之前已经检验过是否为NULL
*/
tree* lca(tree *root, tree *p, tree* q)
{
if (root)
{
if (p == root || q == root) return root;
tree *left = lca(root->left, p, q);
tree *right = lca(root->right, p, q);
if (left && right)
{
return root;
}
if (left) return left;
else return right;
}
}