目录:
一:KMP匹配算法
二:最小生成树算法(Prime以及Kruskal算法)
三:最长公共子序列
四:最长上升子序列
五:字典树
六:扩展欧几里德算法
七:线段树(带有区间更新和懒惰标记)
八:SPFA最短路径算法
九:欧拉回路和欧拉通路的判定
十:快速幂取模
一.KMP匹配算法:
void Kmp_Pre(int num[], int len, int kmp_pre[]) {
int i = 0, j = 0;
j = kmp_pre[0] = -1;
while (i < len) {
while (j != -1 && num[i] != num[j]) {
j = kmp_pre[j];
}
kmp_pre[++i] = ++j;
}}
int Kmp(int num[], int len1, int src[], int len2, int kmp_pre[]) {
int i = 0, j = 0;
Kmp_Pre(num, len1, kmp_pre);
while (i < len2) {
while (j != -1 && num[j] != src[i]) {
j = kmp_pre[j];
}
i++;
j++;
if (j == len1) {
return i - j + 1;
}
}
return -1;
}
二.最小生成树算法(Prime以及Kruskal算法):
int cost[MAXN][MAXN];struct Edge{
int s, t, d; // s为起点,t为终点,d为权值} edge[MAXM];
/*** prim算法* @param cost: 赋权邻接矩阵* @param n: 节点的编号,为1-n*/int prim(int cost[][MAXN], int n){
int rst = 0;
bool vis[MAXN]; // 标记节点是否被加入
int label[MAXN]; // 选择节点的依据
memset(vis, false, sizeof(vis));
vis[1] = true; // 从任意一个节点开始
for (int i = 1; i <= n; ++i) {
label[i] = cost[1][i];
}
for (int times = 1; times <= n - 1; ++times) { // 循环n - 1次,每次加入一个节点
int minEdge = INF;
int choosed = -1;
for (int i = 1; i <= n; ++i) { //选择可扩张边上的节点
if (!vis[i] && label[i] < minEdge) {
minEdge = label[i];
choosed = i;
}
}
if (minEdge == INF) { // mincost没更新,说明原图没有联通
return -1;
}
rst += minEdge;
vis[choosed] = true;
for (int i = 1; i <= n; ++i) { // 更新节点的标记值
if (!vis[i] && cost[choosed][i] < label[i]) {
label[i] = cost[choosed][i];
}
}
}
return rst;
}
void addEdge(int id, int s, int t, int d){
edge[id].s = s;
edge[id].t = t;
edge[id].d = d;}
bool cmp(Edge edge1, Edge edge2){
return edge1.d < edge2.d;}
int unionFind(int pre[], int x) {
int r = x;
while (pre[r] != r) {
r = pre[r];
}
return r;}
/*** Kruskal算法* @param*/int kruskal(Edge edge[], int n, int m){
int rst = 0;
int pre[MAXN]; // 并查集
for (int i = 1; i <= n; ++i) {
pre[i] = i;
}
sort(edge, edge + m, cmp); // 对所有边按权值从小到大排序
int cnt = 0; // 对加入的边计数
for (int i = 0; i < m; ++i) {
int r1 = unionFind(pre, edge[i].s);
int r2 = unionFind(pre, edge[i].t);
if (r1 != r2) { //不构成环, 就加入生成树
pre[r1] = r2;
rst += edge[i].d;
cnt++;
}
if (cnt >= n - 1) { // 已经加入n - 1条边,可以结束了
break;
}
}
if (cnt < n - 1) { // 加入的边小于n - 1,说明原图不联通
return -1;
}
return rst;
}
三.最长公共子序列:
int main() {
freopen("input.txt", "r", stdin);
std::ios::sync_with_stdio(false);
//freopen("output.txt", "w+", stdout);
int cas;
while (cin >> cas) {
while (cas--) {
memset(dp, 0, sizeof(dp));
int n1, n2;
cin >> n1;
for (int i = 1; i <= n1; i++) {
cin >> num1[i];
}
cin >> n2;
for (int i = 1; i <= n2; ++i) {
cin >> num2[i];
}
int Max = 0;
for (int i = 1; i <= n1; i++) {
Max = 0;
for (int j = 1; j <= n2; j++) {
if (num1[i] > num2[j] && Max < dp[j]) {
Max = dp[j];
}
if (num1[i] == num2[j]) {
dp[j] = Max + 1;
}
}
}
Max = 0;
for (int i = 1; i <= n2; i++) {
if (dp[i] > Max) {
Max = dp[i];
}
}
cout << Max << endl;
}
}
return 0;
}
四.最长上升子序列:
如果写成递推公式,我们可以得到 dp[i]=max(dp[j](0<=j<i))+(a[i]>a[j]?1:0)
。
于是我们就能够得到O(n^2)的动态规划方法的实现:
const int MAXN = 1010;int n;int a[MAXN];int dp[MAXN];
int lis()
{
memset(dp, 0, sizeof(dp));
int Max;
for (int i = 0; i < n; ++i)
{
Max = 0;
for (int j = 0; j < i; ++j)
{
if (a[i] > a[j])
{
Max = max(Max, dp[j]);
}
}
dp[i] = Max + 1;
}
Max = 0;
for (int i = 0; i < n; ++i)
{
if (dp[i] > Max) Max = dp[i];
}
return Max;
}
O(nlogn)的动态规划+二分方法
在前一种方法中,我们花费了很多时间在寻找最大的dp[j]上。如果有办法让这个dp[j]变成一个递增的序列,我们就能使用二分来进行优化,从而使得复杂度下降为O(nlogn)了。幸运的是,这种方法确实是存在的。我们可以使用dp[i]来保存在前i个数中最大的那个数,很容易可以理解,这个dp[i]已经是单调不减的。接下来的处理其实有些贪心的思想,对于每一个a[i],我们都在dp数组中寻找比它大的第一个数的下标,不妨设为pos,然后用a[i]来更新dp[pos]。于是我们可以明白,len就应当是max(len, pos+1)。
在这里我们使用 lower_bound函数 ,这个函数将会返回小于等于val的第一个值的指针,如果不存在就返回end指针。
const int MAXN = 1010;int n;int a[MAXN];int dp[MAXN];
int lis()
{
memset(dp, 0, sizeof(int)*n);
int len = 1;
dp[0] = a[0];
for (int i = 1; i < n; ++i)
{
int pos = lower_bound(dp, dp + len, a[i]) - dp;
dp[pos] = a[i];
len = max(len, pos + 1);
}
return len;
}
五.字典树:
struct Node {
int End;//代表以此结为的字符串有多少个
int son[26];
int deep;//字符串的第几个字符}trie[MAXN];
int new_tire() {
triecnt++;
trie[triecnt].End = 0;
for (int i = 0; i < 26; i++) {
trie[triecnt].son[i] = 0;
}
return triecnt;}void init_trie() {
triecnt = 0;
root = new_tire();}void insert_str(char str[]) {
int len = strlen(str);
int rt = root;
for (int i = len - 1; i >= 0; i--) {//题目要求是求后缀相关就如此存储,反之则正向储存
int id = str[i] - 'a';
if (trie[rt].son[id] == 0) {
trie[rt].son[id] = new_tire();
rt = trie[rt].son[id];
trie[rt].End++;
trie[rt].deep = len - i;
} else {
rt = trie[rt].son[id];
trie[rt].End++;
trie[rt].deep = len - i;
}
}
}
六.扩展欧几里德算法:
求a * x + b * y = n的整数解的一般步骤:
①先计算Gcd(a,b),若n不能被Gcd(a,b)整除,则方程无整数解;否则,在方程两边同时除以Gcd(a,b),
得到新的不定方程a' * x + b' * y = n',此时Gcd(a',b')=1;
②利用上面所说的欧几里德算法求出方程a' * x + b' * y = 1的一组整数解x0,y0,则n' * x0, n' * y0是方程a' * x + b' * y = n'的一组整数解;
③根据数论中的相关定理,可得方程a' * x + b' * y = n'的所有整数解为:
x = n' * x0 + b' * t
y = n' * y0 - a' * t
(t为整数)
上面的解也就是a * x + b * y = n 的全部整数解。
补充一个网站看到的简便方法,最小非负整数解为(n' * x0 % b' + b') % b'。
Int ExGcd(Int a, Int b, Int &x, Int &y) {/*注意这个题要求解方程时同时求出最大公约数,公约数单独求会超时= =*/
if (b == 0) {
x = 1;
y = 0;
return a;
}
Int gcd = ExGcd(b, a % b, x, y);
Int t = y;
y = x - a / b * y;
x = t;
return gcd;
}
int main() {
//freopen("input.txt", "r", stdin);
Int x, y, m, n, L;
while (scanf("%I64d %I64d %I64d %I64d %I64d", &x, &y, &m, &n, &L) != EOF) {
Int a = n - m;
Int b = L;
Int c = x - y;
Int x1, y1;
Int gcd = ExGcd(a, b, x1, y1);
if (c % gcd) {
printf("Impossible
");
continue;
}
a /= gcd;
b /= gcd;
c /= gcd;
Int ans = (c * x1 % b + b) % b;
printf("%I64d
", ans);
}
return 0;
}
七.线段树(带有区间更新和懒惰标记):
const int MX = 100050;long long sum[MX<<2];long long sign[MX<<2];int T, n, Q, X, Y;long long Z;
void PushUp(int rt) {
sum[rt] = sum[rt<<1] + sum[rt<<1|1];}void PushDown(int rt, int len) {
if (sign[rt]) {
sign[rt<<1] += sign[rt];
sign[rt<<1|1] += sign[rt];
sum[rt<<1] += (len - (len>>1)) * sign[rt];/*为了解决奇数所带来的子节点分配不平衡问题,因此需要这样做*/
sum[rt<<1|1] += (len>>1) * sign[rt];
sign[rt] = 0;
}
}
void Build(int l, int r, int rt) {
sign[rt] = 0;//初始化标记
if (l == r) {
scanf("%lld", &sum[rt]);
return ;
}
int m = (l + r)>>1;
Build(lson);
Build(rson);
PushUp(rt);
}
void Update(int L, int R, long long z, int l, int r, int rt) {
if (L <= l && r <= R) {
sign[rt] += z;//记录下更新的值
sum[rt] += z * (r - l + 1);//批量更新
return ;
}
PushDown(rt, r - l + 1);//检查标记,看是否需要继续向下更新
int m = (l + r)>>1;
if (L <= m) Update(L, R, z, lson);
if (R > m) Update(L, R, z, rson);
PushUp(rt);
}
long long Query(int L, int R, int l, int r, int rt) {
if (L <= l && r <= R) {
return sum[rt];
}
PushDown(rt, r - l + 1);//别忘了查询时也要注意标记是否存在
int m = (l + r)>>1;
long long ret = 0;
if (L <= m) ret += Query(L, R, lson);
if (R > m) ret += Query(L, R, rson);
return ret;
}
八.SPFA最短路径算法:
struct Node {
int v;
int w;
int nxt;
} edge[MAXN];
long long a, b;int n, m;int edgecnt;
void Add_Edge(int u, int v) {
edge[edgecnt].v = v;
edge[edgecnt].w = a;
edge[edgecnt].nxt = head[u];
head[u] = edgecnt++;
}
long long Spfa() {
memset(vis, false, sizeof(vis));
memset(dis, 0x3f, sizeof(dis));//记得是初始化为无穷大= =
dis[1] = 0;
queue<int>q;
while (!q.empty()) q.pop();
vis[1] = true;
q.push(1);
while (!q.empty()) {
int now = q.front();
q.pop();
for (int i = head[now]; i != -1; i = edge[i].nxt) {
int v = edge[i].v;
int w = edge[i].w;
if (dis[v] > dis[now] + w) {
dis[v] = dis[now] + w;
if (!vis[v]) {
vis[v] = true;
q.push(v);
}
}
}
}
return dis[n] < b ? dis[n] : b;
}
//下面的用于求补图的最短路径,使用到了set,而且要求补图的权值相同,权值不同的算法另算
long long Bfs() {
dis[n] = INF;
set<int>st, ts;
st.clear();
ts.clear();
for (int i = 2; i <= n; i++) {
st.insert(i);
}
queue<int>q;
while (!q.empty()) q.pop();
q.push(1);
dis[1] = 0;
while (!q.empty()) {
int now = q.front();
q.pop();
for (int i = head[now]; i != -1; i = edge[i].nxt) {
int v = edge[i].v;
if (st.count(v) == 0) {
continue;
}
st.erase(v);
ts.insert(v);
}
set<int>::iterator it = st.begin();
for ( ; it != st.end(); it++) {
q.push(*it);
dis[*it] = dis[now] + 1;
}
st.swap(ts);
ts.clear();
}
return dis[n] * b < a ? dis[n] * b : a;
}
九.欧拉回路和欧拉通路的判定:
①欧拉通路: 通过图中每条边且只通过一次,并且经过每一顶点的通路。
②欧拉回路: 通过图中每条边且只通过一次,并且经过每一顶点的回路(可以回到原点,就是出发的那个点)。
无向图是否具有欧拉通路或回路的判定:
①欧拉通路:图连通;图中只有0个或2个度为奇数的节点;
②欧拉回路:图连通;图中所有节点度均为偶数;
有向图是否具有欧拉通路或回路的判定:
①欧拉通路:图连通;除2个端点外其余节点入度=出度;1个端点入度比出度大1;一个端点入度比出度小1 或 所有节点入度等于出度;
②欧拉回路:图连通;所有节点入度等于出度。
十.快速幂取模:
int main(){
//freopen("input.txt", "r", stdin);
int a, b;
while (scanf("%d%d", &a, &b), a || b) {
int ans = 1;
a %= 1000;
while (b > 0) {//快速幂取模
if (b % 2 == 1) {
ans = (ans * a) % 1000;//如果幂的次数是奇数,就优先一步乘入结果,让幂的次数变偶数
}
b>>=1;
a = (a * a) % 1000;//例如让2的四次方变成4的二次方,从而减少运算次数
}
printf("%d
", ans);
}
return 0;
}