#include<bits/stdc++.h> usingnamespace std; boolTopSort(vector<vector<int>> &G, int n, vector<int> &inDegree){ int num = 0; //记录加入拓扑排序的顶点数 queue<int> q; for (int i = 0; i < n; i++) if (inDegree[i] == 0) q.push(i); //将所有入度为0的顶点入队 while (!q.empty()) { int u = q.front(); //取队首顶点u cout << u << " "; q.pop(); for (int i = 0; i < G[u].size(); i++) { int v = G[u][i]; //u的后继节点 inDegree[v]--; //v的入度减1 if (inDegree[v] == 0) //顶点v的入度减为0则入队 q.push(v); } G[u].clear(); //清空顶点u的所有出边 num++; } if (num == n) //加入拓扑序列的顶点数为n,说明拓扑排序成功,否则,失败 returntrue; else returnfalse; } intmain(){ int n, m; cout << "请输入顶点数和边数:"; cin >> n >> m; vector<vector<int>> G(n); for (int i = 0; i < m; i++) { int x, y; cout << "请输入第" << i+1 << "条边的顶点:"; cin >> x >> y; G[x].push_back(y); } cout << "拓扑排序为:"; vector<int> inDegree(n); for (auto x : G) { for (auto y : x) inDegree[y]++; } bool res = TopSort(G, n, inDegree); return0; }
booldfs(int u){ c[u] = -1; for (int v : G[u]) { if (c[v] < 0) returnfalse; elseif (!c[v]) if (!dfs(v)) returnfalse; } c[u] = 1; topo.push_back(u); returntrue; }
booltoposort(){ topo.clear(); memset(c, 0, sizeof(c)); for (int u = 0; u < n; u++) if (!c[u]) if (!dfs(u)) returnfalse; reverse(topo.begin(), topo.end()); returntrue; }
voidbuild(int *a, int q, int l, int r) { if (l == r) { st[q] = a[l]; return; } int mid = (l + r) / 2; build(a, q * 2 + 1, l, mid); build(a, q * 2 + 2, mid + 1, r); st[q] = st[q * 2 + 1] + st[q * 2 + 2]; }
//build(a, 1, 1, n);
单点更新
voidupdate_single(int q, int l, int r, int x, int num) { //q为初始节点(一般默认为1),l,r为线段树范围,x为具体节点,num为更新值 if (l == x && r == x) { st[q] += num; return; } int mid = (l + r) / 2; if (mid >= x) update(q * 2 + 1, l, mid, x, num); else update(q * 2 + 2, mid + 1, r, x, num); st[q] = st[q * 2 + 1] + st[q * 2 + 2]; }
只单点更新的区间求和
intquery(int q, int l, int r, int tl, int tr) { //tl,tr为求和区间 if (tl <= l && r <= tr) return st[q]; int mid = (l + r) / 2, sum = 0; if (tl <= mid) sum += query(q * 2 + 1, l, mid, tl, tr); if (tr > mid) sum += query(q * 2 + 2, mid + 1, r, tl, tr); return sum; }
区间更新
//区间[tl,tr]的值全部加num voidupdate_region(int q, int l, int r, int tl, int tr, int num) { if (l >= tl && r <= tr) { st[q] += num * (r - l + 1); lazy[q] += num; return; } if (lazy[q]) pushdown(q, l, r); int mid = (l + r) / 2; if (tl <= mid) update_region(q * 2, l, mid, tl, tr, num); if (tr > mid) update_region(q * 2 + 1, mid + 1, r, tl, tr, num); st[q] = st[q * 2] + st[q * 2 + 1]; }
int query(int q, int l, int r, int tl, int tr) { if(l>=tl&&r<=tr) return st[q]; if(lazy[q]) pushdown(q, l, r);//下放标记 int sum = 0; int mid = (l + r) / 2; if (tl <= mid) sum += query(q * 2, l, mid, tl, tr); if (tr > mid) sum += query(q * 2 + 1, mid + 1, r, tl, tr); return sum; }
#include<bits/stdc++.h> usingnamespace std; constint maxn=1e7; int trie[maxn][26]; string s; int ind=1; int flag[maxn]; voidinsert(string s)//插入操作 { int t = 0; for (int i = 0; s[i]; i++) { int x = s[i] - 'a'; //x是t节点的第x个孩子 if (trie[t][x] == 0) //如果没有就添加节点 trie[t][x] = ind++; //ind为节点编号 t = trie[t][x]; ans[t]++; } // flag[t]=1; //用flag标记字符串的最后一个节点位置,查询的时候可以防止出错,如树中有cart,不添加标记如果查询car就会出现错误 }
boolquery(string s)//查询是否存在目标字符串 { int t = 0; for (int i = 0; s[i]; i++) { int x = s[i] - 'a'; if (trie[t][x] == 0) //中途没有查询到就返回false return0; t = trie[t][x]; //前往下一个节点 } return flag[t]; //插入时的标记在此发挥作用 }
最短路问题
四种算法
Floyd算法
Dijkstra算法
Bellman-Ford算法
SPFA算法
Floyd算法
用来求任意两个节点之间的最短路
缺点:复杂度较高为O(n^3),一般只适合三位数的数据大小。
优点:适用于任何图,代码简单,边权可以为负,但是不能有负环(必须存在最短路)。
核心代码
for (k = 1; k <= n; k++) { for (x = 1; x <= n; x++) { for (y = 1; y <= n; y++) { f[x][y] = min(f[x][y], f[x][k] + f[k][y]); } } }
原本f数组是有三维的,但是第一维可以省略不会产生影响,这里不进行深究。
Dijkstra算法
用来求单源最短路的算法,图的边权不能为负,时间复杂度为O(N^2),堆优化为O(mlogn)。
模板代码
#include<iostream> #include<cmath> #include<set> #include<string.h> #include<queue> #include<map> #include<stdio.h> #include<algorithm> #include<math.h> usingnamespace std; constint inf=0x3f3f3f3f; constint maxn = 1e3 + 10; int n,m;//n个顶点,m条边。 bool visited[maxn];//判断是否确定到源点的最终最短距离。 int graph[maxn][maxn]; int dis[maxn];//顶点到源点的最短距离。 int start,goal;
voidinit() { memset(visited, false, sizeof(visited)); for (int i = 1; i <= n; i++) { dis[i] = graph[start][i]; //从start点到i点的距离 } }
voiddijkstra() { int minn;//记录每趟最短路径中最小的路径值。 int pos = 0; //记录得到的minn所对应的下标。 init(); visited[start] = true; //因为pos为0,所以这里标不标记都一样 for (int i = 1; i <= n; i++) { //将n个顶点依次加入判断。 minn = inf; for (int j = 1; j <= n; j++) { if (!visited[j] && dis[j] < minn) { minn = dis[j]; pos = j; } } //经过这趟for循环后找到的就是我们想要的点,可以确定这点到源点的最终最短距离了。 visited[pos] = true; //将此点并入已知集合。 //接下来就是更新dis数组了,也就是当前最短距离,这都是针对还没有并入已知集合的点。 for (int j = 1; j <= n; j++) { if (!visited[j] && dis[j] > dis[pos] + graph[pos][j]) dis[j] = dis[pos] + graph[pos][j]; } } //退出循环后,所有的点都已并入已知集合中,得到的dis数组也就是最终最短距离了。 if (dis[goal] == inf) cout << -1 << endl; else cout << dis[goal] << endl; //输出目标点到源点的最短路径长度。 }
intmain() { while (cin >> n >> m && n && m) { memset(graph, inf, sizeof(graph)); int u, v, w; for (int i = 0; i < m; i++) { cin >> u >> v >> w; if (w < graph[u][v])// 判断重边,取最小值 graph[u][v] = w; if (w < graph[v][u]) graph[v][u] = w; } start = 1, goal = n; dijkstra(); } return0; }
Bellman-Ford算法
能找到某个结点出发到所有结点的最短路,或者报告某些最短路不存在
可以在负权图中使用,可以用来寻找是否有负环
核心代码
for(int k = 1 ; k <= n - 1 ; k ++) //n为点 { for(int i = 1 ; i < m ; i ++) //m为边,枚举每一条边 { if(dis[v[i]] > dis[u[i]] + w[i])//尝试对每一条边进行松弛 dis[v[i]] = dis[u[i]] + w[i] ;//dis数组记录源点到其余各个顶点的最短路径 } }
模板例题+代码
题目
第1行输入n,m,其中n为顶点,m为边的关系
第2-m+1行给出两个点和之间的距离
求1号点到每个点的最短路
题解
#include<bits/stdc++.h> constint INF = 0x3f3f3f; usingnamespace std; intmain() { int u[100] , v[100] , w[100] , dis[100] , n , m ; cin >> n >> m; for(int i = 1 ; i <= m ; i ++) { cin >> u[i] >> v[i] >> w[i]; } for(int i = 1 ; i <= n ; i ++) dis[i] = INF; dis[1] = 0; for(int k = 1 ; k <= n - 1 ; k ++) for(int i = 1 ; i <= m ; i ++) if(dis[v[i]] > dis[u[i]] + w[i]) dis[v[i]] = dis[u[i]] + w[i]; for(int i = 1 ; i <= n ; i ++) cout << dis[i] << " "; return0 ; }
voidmerge(int x,int y)//合并函数 { int a = find(x); int b = find(y); if (a != b) pre[a] = b; }
voidkrusal() { sort(edge, edge + m, cmp);//排序 for (int i = 0; i < m; i++) { int x = edge[i].u; int y = edge[i].v; if (find(x) != find(y)) //两个点的父节点不是同一个就直接合并,如果是同一个的话就直接丢弃这个边,继续下一个边 { merge(x, y); num++; ans += edge[i].w; if (num == n - 1)//根据性质,最小生成树的边是结点数-1,,如果边数够了就表明满足条件 { cout << ans << endl; return; } } }//循环结束边数都不够说明无法构成最小生成树 cout << "orz" << endl; return; }
intmain() { cin >> n >> m; init(); for (int i = 0; i < m; i++) { cin >> edge[i].u >> edge[i].v >> edge[i].w; } krusal(); return0; }
Prim算法
知识点:对点贪心
思想:每次选择距离当前节点最近的一个节点,并用这条边更新其它节点的距离。
模板代码
#include<iostream> #include<cstring> usingnamespace std; constint inf=0x3f3f3f; constint maxx=500+10; int e[maxx][maxx], dis[maxx]; bool book[maxx]; int n,m; voidinit()//初始化,自己到自己初始化为0,到其他点为inf { for (int i = 0; i < m; i++) for (int j = 0; j < m; j++) if (i == j) e[i][j] = 0; else e[i][j] = inf; }
intprim(int e[][maxx], int n) { int ans = 0; memset(book, false, sizeof(book)); for (int i = 0; i < n; i++) dis[i] = e[0][i]; for (int i = 0; i < n; i++) { int minn = inf, u = -1; for (int j = 0; j < n; j++) { if (!book[j] && dis[j] < minn) //未被标记且距离更小 { minn = dis[j]; u = j; } } if (u == -1) return0; ans += minn; book[u] = true;//访问后进行标记 for (int v = 0; v < n; v++) if (!book[v] && e[u][v] != inf) dis[v] = min(dis[v], e[u][v]); } return ans; }
intmain() { cin >> m >> n; init(); for (int i = 0; i < n; i++) { int a, b, c; cin >> a >> b >> c; if (e[a][b] > c) e[a][b] = e[b][a] = c;//无向图 } int cnt = prim(e, m); if (cnt) cout << cnt << endl; else cout << "orz" << endl;//没有最小生成树 return0; }
#include<bits/stdc++.h> #define MXN 50007 usingnamespace std; vector<int> v[MXN]; vector<int> w[MXN]; int fa[MXN][31], cost[MXN][31], dep[MXN]; int n, m; int a, b, c; // 接受两个参数:dfs 起始节点和它的父亲节点。 voiddfs(int root, int fno){ // 初始化:第 2^0 = 1 个祖先就是它的父亲节点,dep 也比父亲节点多 1。 fa[root][0] = fno; dep[root] = dep[fa[root][0]] + 1; // 初始化:其他的祖先节点:第 2^i 的祖先节点是第 2^(i-1) 的祖先节点的第 // 2^(i-1) 的祖先节点。 for (int i = 1; i < 31; ++i) { fa[root][i] = fa[fa[root][i - 1]][i - 1]; cost[root][i] = cost[fa[root][i - 1]][i - 1] + cost[root][i - 1]; } // 遍历子节点来进行 dfs。 int sz = v[root].size(); for (int i = 0; i < sz; ++i) { if (v[root][i] == fno) continue; cost[v[root][i]][0] = w[root][i]; dfs(v[root][i], root); } }
倍增算法
时间复杂度为O(nlogn)
基础模板代码
#include<bits/stdc++.h> using namespace std; const int maxn = 5e5 + 5; int n, m, s; int lg[maxn]; int dep[maxn]; int fa[maxn][25]; vector<int> G[maxn];
void getdep(int u) { //求祖先数组 for (int i = 1; i <= lg[dep[u]]; i++) fa[u][i] = fa[fa[u][i - 1]][i - 1]; for (int i = 0; i < G[u].size(); i++) { //遍历u所有边 int e = G[u][i]; if (e != fa[u][0]) { fa[e][0] = u; dep[e] = dep[u] + 1; getdep(e); } } }
int lca(int u,int v) { if (dep[u] < dep[v]) swap(u, v); while (dep[u] != dep[v]) u = fa[u][lg[dep[u] - dep[v]]]; if (u == v) return u; for (int j = lg[dep[u]]; j >= 0; j--) { if (fa[u][j] != fa[v][j]) { u = fa[u][j]; v = fa[v][j]; } } return fa[u][0]; }
int main() { cin >> n >> m; for (int i = 1; i <= n; i++) { lg[i] = lg[i - 1]; if (i == (1 << lg[i - 1])) lg[i]++; } for (int i = 1; i <= n; i++) lg[i]--; for (int i = 1; i <= n; i++) { int u, v; cin >> u >> v; G[u].push_back(v);//无向图 G[v].push_back(u); } //无向图可以用任意一个节点当做根节点 //选择初始化1的深度为1,以1为根节点建树 dep[1] = 1; getdep(1); for (int i = 1; i <= m; i++) { int x, y; cin >> x >> y; cout << lca(x, y) << endl; } return 0; }
tarjan(u){ DFN[u]=Low[u]=++Index //为节点u设定次序编号和Low初值 Stack.push(u) //将节点u压入栈中 foreach(u,v) in E //枚举每一条边 if(v is not visted) //如果节点v未被访问过 tarjan(v) //继续向下找 Low[u]=min(Low[u],Low[v]) elseif(v in S) //如果节点v还在栈内 Low[u]=min(Low[u],DFN[v]) if(DFN[u]==Low[u]) //如果节点u是强连通分量的根 repeat v=S.pop//将v退栈,为该强连通分量中一个顶点 print v until(u==v) }
structedge { int to; bool exists; int revref; booloperator<(const edge &b) const { return to < b.to; } };
vector<edge> beg[505]; int cnt[505];
constint dn = 500; stack<int> ans;
voidHierholzer(int x) { for (int &i = cnt[x]; i < (int)beg[x].size();) { if (beg[x][i].exists) { edge e = beg[x][i]; beg[x][i].exists = 0; beg[e.to][e.revref].exists = 0; ++i; Hierholzer(e.to); } else { ++i; } } ans.push(x); }
int deg[505]; int reftop[505];
intmain() { for (int i = 1; i <= dn; ++i) { beg[i].reserve(1050); }
int m; scanf("%d", &m); for (int i = 1; i <= m; ++i) { int a, b; scanf("%d%d", &a, &b); beg[a].push_back((edge){b, 1, 0}); beg[b].push_back((edge){a, 1, 0}); ++deg[a]; ++deg[b]; }
for (int i = 1; i <= dn; ++i) { if (!beg[i].empty()) { sort(beg[i].begin(), beg[i].end()); // 为了要按字典序贪心,必须排序 } }
for (int i = 1; i <= dn; ++i) { for (int j = 0; j < (int)beg[i].size(); ++j) { beg[i][j].revref = reftop[beg[i][j].to]++; } }
int bv = 0; for (int i = 1; i <= dn; ++;i) { if (!deg[bv] && deg[i]) { bv = i; } elseif (!(deg[bv] & 1) && (deg[i] & 1)) { bv = i; } }
Hierholzer(bv);
while (!ans.empty()) { printf("%d\n", ans.top()); ans.pop(); } return0; }
题解2
#include<iostream> #include<cstdio> #include<cmath> usingnamespace std; int map[10001][10001];//记录两个点之间的路径个数 int du[10001];//辅助记录奇点 int lu[10001];//记录路径 int n,x,y,js=0; int maxn=0; voidfind(int i)// { int j; for(j=1;j<=maxn;++j)//而且这里不是n而是maxn因为n不是点的个数而是下面有多少行 { if(map[i][j]>=1) { map[i][j]--;//删去边一次吗避免重复 map[j][i]--;//z这里和一笔画不一样这里是累减而一笔画直接变成0 find(j); } } lu[++js]=i; } intmain() { scanf("%d",&n); for(int i=1;i<=n;++i) { scanf("%d%d",&x,&y); map[x][y]++; map[y][x]++; du[x]++; du[y]++;//记录出现的次数 maxn=max(maxn,max(x,y)); } int start=1;//默认奇点是1 for(int i=1;i<=maxn;++i) { if(du[i]%2)//找到奇点 { start=i;//记录奇点 break;//然后结束循环 } } find(start);//从奇点开始找 for(int i=js;i>=1;i--) { printf("%d\n",lu[i]);//挨个输出路径并且换行 } return0; }
//dfs染色法 #include<algorithm> #include<cstring> #include<iostream> usingnamespace std; constint N = 1e5 + 10, M = 2e5 + 10; int n, m; int h[N], e[M], ne[M], idx; //邻接表的存储 int color[N];
voidadd(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; }
booldfs(int u, int c) { color[u] = c; //当前这个点u的颜色是 c
for (int i = h[u]; i != -1; i = ne[i]) { int j = e[i]; if (!color[j]) //u 的邻接点 j 未被染色 { dfs(j, 3 - c); // u的颜色如果是1,j就是3-1=2;u的颜色如果是2,j就是3-2=1 } elseif (color[j] == c) returnfalse; //两邻接点染相同颜色 } returntrue; }
intmain() { cin >> n >> m; memset(h, -1, sizeof(h));
while (m--) { int a, b; scanf("%d%d", &a, &b); add(a, b), add(b, a); // 无向图建两条相反方向的边 }
bool flag = true; for (int i = 1; i <= n; i++) //遍历图的所有点 if (!color[i]) { if (!dfs(i, 1)) { flag = false; break; } }
if (flag) cout << "Yes"; else cout << "No"; }
//bfs染色法 //不建议使用 //太玄幻了根本看不懂还是把代码粘贴进来吧 #include<algorithm> #include<cstring> #include<iostream> #include<queue> usingnamespace std; typedef pair<int, int> PII; //first存点编号,second存颜色 constint N = 1e5 + 10, M = 2e5 + 10; int n, m; int h[N], e[M], ne[M], idx; //邻接表的存储 int color[N];
voidadd(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; }
#include<bits/stdc++.h> usingnamespace std; constint maxn = 600 + 5; bool line[maxn][maxn]; //line[x][y]=true表示x号女生喜欢y男生(边) int boy[maxn]; //存储y号男生匹配边另一端的匹配点女生,如果是未覆盖点则为0 bool falg[maxn]; //存储y号男生是否在这条交替路上被使用过,男生是否被别人喜欢 int k, n, m; booldfs(int x) {
for (int j = 1; j <= m; j++) { if (line[x][j] && !falg[j]) { falg[j] = true; if (!boy[j] || dfs(boy[j])) { //满足上面的判断语句说明找到了增广路 //即终点是未覆盖点的交替路 boy[j] = x; returntrue; } } //该条路不是增广路 }
returnfalse; } intmain() { while (cin >> k) { if (!k) break; cin >> n >> m; int x, y; int ans = 0; memset(line, false, sizeof(line));//初始化 memset(boy, 0, sizeof(boy)); while (k--) { cin >> x >> y; line[x][y] = true; } for (int i = 1; i <= n; i++) { memset(falg, false, sizeof(falg));//每次循环都要初始化flag if (dfs(i)) ans++; } cout << ans << endl; } return0; }
C++高精度
高精度四则运算
#include<bits/stdc++.h> usingnamespace std;
structbign//大整数数组 { int len; //位数 int num[1000]; }; //大整数转换 bign change(char a[]) { bign c; c.len = strlen(a); for (int i = 0; i < c.len; i++) { c.num[i] = a[c.len - i - 1] - '0'; } return c; } //大整数比较 intcompare(bign a, bign b) { //大于返回1,小于返回-1,相等返回0 if (a.len > b.len) //位数多的数比位数少的数大 return1; if (a.len < b.len) return-1; for (int i = a.len - 1; i >= 0; i--) { //位数相同,高位到低位逐位比较 if (a.num[i] > b.num[i]) return1; elseif (a.num[i] < b.num[i]) return-1; } return0; //执行到最后肯定相等 }
bign add_bign(bign a, bign b) { bign c; for (int i = 0; i < a.len || i < b.len; i++) { //以位数长的结束条件 c.num[c.len] += a.num[i] + b.num[i]; //对应位置相加再加上进位 if (c.num[c.len] >= 10) { //大于等于10需要进位 c.num[c.len + 1]++; //进位加到前一位上 c.num[c.len] -= 10; //更新原位置的值 } c.len++; } if (c.num[c.len]) //最高位不为零长度加一 c.len++; return c; }
bign sub_bign(bign a, bign b) { bign c; for (int i = 0; i < a.len || i < b.len; i++) { //以位数长的结束条件 if (a.num[i] < b.num[i]) { //如果同位的被减数比减数小需要借位 a.num[i] += 10; a.num[i + 1]--; } c.num[c.len++] = a.num[i] - b.num[i]; } while (c.len > 1 && !c.num[c.len - 1]) //消除前缀零 c.len--; return c; }
int a[100]; int dp[100]; intmain() { int n; fill(dp, dp + sizeof(dp), 1); cin >> n; for (int i = 0; i < n; i++) cin >> a[i]; for (int i = 0; i < n; i++) //两个for循环遍历整个数组并求dp for (int j = 0; j < i; j++) { if (a[i] > a[j])//满足次条件才能形成升序列 dp[i] = max(dp[i], dp[j] + 1); }
for (int i = 0; i < n; i++) cout << dp[i] << ' ';
return0; }
二分+贪心优化
#include<iostream> #include<string.h> #include<algorithm> using namespace std; const int maxn = 1e5 + 10; int num[maxn]; const int INF = 0x3f3f3f3f; int slow[maxn], d[maxn];
int main() { fill(slow, slow + maxn, INF);//fill函数初始化 int n; cin >> n; for (int i = 1; i <= n; i++) cin >> num[i]; int len = -1; for (int i = 1; i <= n; i++) { int j = lower_bound(slow + 1, slow + maxn + 1, num[i]) - slow;//二分查找 d[i] = j; if (len < d[i]) len = d[i]; slow[j] = num[i]; }
#include <algorithm> #include <iostream> #include <string.h> using namespace std; #define IOS std::ios::sync_with_stdio(false) const int N = 1010; int n, m; char a[N], b[N]; int dp[N][N]; int main() { memset(dp, 0, sizeof(dp)); cin >> n >> m; for (int i = 1; i <= n; ++i) cin >> a[i];//A字符串 for (int j = 1; j <= m; ++j) cin >> b[j];//B字符串 for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j) if (a[i] == b[j]) dp[i][j] = dp[i - 1][j - 1] + 1; //左上角的值+1 else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);//左边或上边取最大
cout << dp[n][m] << endl;//最长公共序列
//以下是输出该序列 int i = n, j = m, index = 0; char lcs[1005]; while (i != 0 && j != 0) { if (dp[i][j] == dp[i][j - 1])//反向回退 j--; else if (dp[i][j] == dp[i - 1][j]) i--; else { lcs[index++] = a[i]; i--, j--; } } for (int i = index - 1; i >= 0; --i)//最后一步会index会多加一个1所以要在这里减掉 cout << lcs[i]; return 0; }
LCS表格
推一遍就懂了
此处图片丢失
背包
01背包
题目
有N件物品和一个容量为V的背包。第i件物品的费用是w [ i ] ,价值**是v [ i ] **,求将哪些物品装入背包可使价值总和最大。
状态转移方程
核心代码
memset(dp, 0, sizeof(dp)); for (int i = 1; i <= n; i++) { for (int j = 0; j <= m; j++) { if (j >= w[i]) //如果背包装得下当前的物体 { dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]); } else//如果背包装不下当前物体 { dp[i][j] = dp[i - 1][j]; } } }
for (int i = 1; i <= n; i++) { int num = min(p[i], V / w[i]); for (int k = 1; num > 0; k <<= 1) { if (k > num) k = num; num -= k; for (int j = V; j >= w[i] * k; j--) dp[j] = max(dp[j], dp[j - w[i] * k] + v[i] * k); } }
#include<iostream> #include<stdio.h> #include<string.h> usingnamespace std; constint N = 55; int f[N]; voidInit() { f[0] = f[1] = 1; for (int i = 2; i < N; i++) f[i] = f[i - 1] + f[i - 2]; }
intmain() {
Init(); int n; while (cin >> n) { if (n == 0) break; bool flag = 0; for (int i = 0; i < N; i++) { if (f[i] == n) { flag = 1; break; } } if (flag) puts("Second win"); else puts("First win"); } return0; }
例8 :在一张节目单中原有6个节目,若保持这些节目相对次序不变,再添加3个节目,共有几种情况?
-o - o - o - o - o - o - 三个节目abc
可以用一个节目去插7个空位,再用第二个节目去插8个空位,用最后个节目去插9个空位
所以一共是 $C_7^1×C_8^1×C_9^1=504种$
快速幂算法
快速幂
作用:解决乘法溢出问题,如2^100 % 10086
要用到模运算规律:(a * b) % p = (a % p * b % p) % p
朴素版
int ans=1; for (int i=1; i<=b; ++i) { ans *= a % p; } return ans % p;
但是朴素版时间复杂度过高为O(N)
优化版
intpow(int a, int b, int p) { int ans = 1; while (b) { if (b & 1) ans = ans * a % p; //若b&1==1,就选择当前的迭代值a和res累乘。 a = a * a; //迭代构造a,a是初始值的2的整数次幂 b = b >> 1; //将b右移一位 } //以上计算得到a^b return ans % p; //取模 }
龟速乘
作用:当(a * b) % p中a和b过大时,快速幂也会超出数据范围,可以使用龟速乘防止爆掉
//防止在乘法过程中爆ll ll fast_mult(ll a, ll b) { ll res = 0; while (b > 0) { if (b & 1) { res = (res + a) % n; } b >>= 1; a = (a + a) % n; } return res % n; }
矩阵快速幂
矩阵乘法
const int N = 100; int c[N][N]; void multi(int a[][N], int b[][N], int n) //n是矩阵大小,n<N { memset(c, 0, sizeof(c)); for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) for (int k = 1; k <= n; k++) c[i][j] += a[i][k] * b[k][j]; }
矩阵快速幂模板
#include <algorithm> #include <cstdio> #include <iostream> using namespace std; int M, n; struct node //定义一个矩阵类型的结构体 { int m[100][100]; } ans, res; //ans是结果,res是最初的方阵 node mul(node A, node B) { int i, j, k; node temp; //定义一个临时矩阵,存放A*B的结果 for (i = 0; i < n; i++) //先全部定义为0 { for (j = 0; j < n; j++) { temp.m[i][j] = 0; } } for (i = 0; i < n; i++) //矩阵相乘的代码 { for (j = 0; j < n; j++) { for (k = 0; k < n; k++) { temp.m[i][j] += A.m[i][k] * B.m[k][j]; } } } return temp; } void fastpow(int M, int n) { int i, j; for (i = 0; i < n; i++) { for (j = 0; j < n; j++) { if (i == j) { ans.m[i][j] = 1; } else ans.m[i][j] = 0; } } //这里是思想的转换,之前我们定义为1去计算,所以我们先初始化ans为 //单位矩阵,我们知道单位矩阵与任何矩阵的乘积为其本身 while (M) //快速幂的步骤 { if (M & 1) { ans = mul(ans, res); } res = mul(res, res); M = M >> 1; } } int main() { cin >> n; //方阵的阶数 cin >> M; //指数 int i, j; for (i = 0; i < n; i++) { for (j = 0; j < n; j++) { cin >> res.m[i][j]; //初始化方阵res } } fastpow(M, n); //进行快速幂 for (i = 0; i < n; i++) //输出 { for (j = 0; j < n; j++) { cout << ans.m[i][j] << " "; } cout << endl; } return 0; }
intexgcd(int a, int b, int &x, int &y) { if (b == 0) { x = 1, y = 0; return a; } int r = exgcd(b, a % b, x, y); int t = x; x = y; y = t - a / b * y; return r; } intinv(int n, int m) { int x, y; int ans = exgcd(n, m, x, y); if (ans == 1) return (x % m + m) % m; //定义:正整数 a, n,如果有 ax ≡ 1(mod n),则称 x 的最小整数解为 a 模 n的逆元。 else return-1; } intmain() { int n, m; cin >> n >> m; int ans = inv(n, m); ans == -1 ? cout << "no" << endl : cout << ans << endl; return0; } /* intput: 5 7 22 29 100 97 output: 3 4 65 */
费马小定理
定义:若存在整数 a , p 且gcd(a,p)=1,即二者互为质数,则有a^(p-1)≡ 1(mod p)。
费马小定理求逆元
longlongfast_pow(longlong a, longlong b, longlong p) { longlong ans = 1; a %= p; while (b) { if (b & 1) ans = (ans * a) % p; a = (a * a) % p; b >>= 1; } return ans; } longlonginv(longlong x, longlong p) { returnfast_pow(x, p - 2, p); }
#include<cstdio> #include<cstring> #include<iostream> #define Memset(x, a) memset(x, a, sizeof(x)) usingnamespace std; constint N = 1e6 + 10; char w[N], t[N]; int next[N]; int sum;
voidgetNext(constchar P[], int next[]) { int m = strlen(P); int i = 0, j; j = next[0] = -1; while (i < m) { while (-1 != j && P[i] != P[j]) j = next[j]; next[++i] = ++j; } }
voidkmp(constchar T[], constchar P[], int next[]) { int n = strlen(T), m = strlen(P); int i, j; getNext(P, next); i = j = 0; while (i < n) { while (-1 != j && T[i] != P[j]) j = next[j]; i++; j++; if (j >= m) { sum++; j = next[j]; //这儿修改很重要,不然会超时 } } }
intmain() { int T; scanf("%d", &T); while (T--) { sum = 0; Memset(next, 0); scanf("%s%s", w, t); kmp(t, w, next); printf("%d\n", sum); } return0; }
#include<cstdio> #include<cstring> #include<iostream> #define Memset(x, a) memset(x, a, sizeof(x)) usingnamespace std; constint N = 4e5 + 10; int next[N], ans[N]; char s[N];
voidgetNext(constchar P[], int next[]) { int m = strlen(P); int i = 0, j; j = next[0] = -1; while (i < m) { while (-1 != j && P[i] != P[j]) j = next[j]; next[++i] = ++j; } }
intmain() { while (~scanf("%s", s)) { Memset(next, 0); getNext(s, next); int cnt = 0; int len = strlen(s); int j = next[len]; while (j > 0) { ans[++cnt] = j; j = next[j]; } for (int i = cnt; i > 0; i--) printf("%d ", ans[i]); printf("%d\n", len); } return0; }