排序算法
计数排序
void countsort(int* a, int n)//计数排序
{
for (int i = 1; i <= n; i++)
{
cnt[a[i]]++;
}
for (int j = 1; j <= Max; j++)
{
for (int k = 0; k < cnt[j]; k++)
cout << j << " ";
}
}
希尔排序
void ShellSort(int* arr, int n)
//shell排序,基本思想:有点类似于分组排序,数组下标为i的值与下标为i+shell(如果n为奇数,还存在与i+2*shelll进行比较的情况)的值进行比较,如果比他大就交换,反之就小,i从0遍历到n-shell(想想也知道只用遍历一半吧)
//然后shell自除以2,再重复上述操作。
//最后当shell为0时,数组便排列好了。
{
int shell=n;
while (shell > 0)
{
shell /= 2;//希尔增量,shell=shell/3+1也可以
int i = 0;
for (int i = 0; i < n - shell; i++)//折半,所以遍历原数组长度的一半就行
{
int end = i;
int temp = arr[end + shell];
while (end >= 0)
{
if (temp < arr[end])
{
arr[end + shell] = arr[end];
end -= shell;//这个位置很关键,这样才能分别比较三项及以上的数,并且交换位置。
}
else
break;
}
arr[end+shell] = temp;
}
}
}
归并排序
void msort(int l, int r)
//归并排序,基本思想:左端点指针与右端点指针,将其从中间分割,对左半部分进行递归,右半部分进行递归,
//然后从头分别遍历左半部分与右半部分,哪边小就把哪边的数追加到新的数组之中,如果左右半边数组有没有被遍历到的情况,则直接将后面的元素全部追加到新数组后面;
//最后通过新的已经排好的数组更新原来的数组,
//递归,后面由边界到抽象,逐渐合并为一个已经排好的数组。
{
if (l == r) return;
int m = (l + r) / 2;
msort(l, m);//对左半部分递归
msort(m + 1, r);//对右半部分递归
merge(l, m, r); //合并数组
}
void merge(int l, int m, int r)//合并数组
{
int i = l, j = m + 1,k=l;
while (i <= m && j <= r)//从头遍历数组的左半部分和右半部分,哪边小就把哪边的数组加到新数组中
{
if (arr[i] > arr[j])
arr1[k++] = arr[j++];
else
arr1[k++] = arr[i++];
}
//后面是考虑左右半边数组有没被遍历到的情况,如果有就直接添加到新数组后面
while (i <= m)
arr1[k++] = arr[i++];
while (j <= r)
arr1[k++] = arr[j++];
for (int i = l; i <= r; i++)//将已经排好的数组重新添加到原数组中
arr[i] = arr1[i];
}
插入排序
void insertsort(int a[],int n)
//插入排序,基本思想:左半部分为有序区,右半部分为无序区,从有序区右端依次向左与无序区第一个值进行比较,
//如果比这个值大,则该值向右移,如果比这个值小,则插入这个值的右端,合并为新的有序区
{
for (int i = 1; i < n; i++)
{
int now = a[i];
int j;
for (j = i - 1; j >= 0; j--)
{
if (now < a[j]) a[j + 1] = a[j];//若a[j]大于now,则往前移,直到不大于
else
break;
}
a[j + 1] = now;//因为j--,所以这里是j+1;
}
}
快速排序
void quicksort(int a[], int l, int r)
//快速排序:基本思想为找到一个“哨兵”值,起始端和终止端各有一个指针i,j,
//如果指针i指向的值大于哨兵值,j指向的值小于哨兵值,则进行交换,直到左半部分的值全部小于哨兵值,右半部分的值全部大于哨兵值,然后对左右半部分进行递归,最后可以排序成功
{
int i = l, j = r, tag = a[(l + r) / 2],temp;
do {
while (a[i] < tag) i++;
while (a[j] > tag) j--;
if (i <= j)
{
temp = a[i];
a[i] = a[j];
a[j] = temp;
i++; j--;//交换值,并且将两端指针各往前挪一格,方便之后继续进行交换操作
}
} while (i <= j);
if (l < j) quicksort(a, l, j);//左半部分进行递归
if (r > i) quicksort(a, i, r);//右半部分进行递归
}
基数排序
int maxbit(int data[], int n)
{
int maxData = data[0];
for (int i = 1; i < n; ++i)
{
if (maxData < data[i])
maxData = data[i];
}
int d = 1;
int p = 10;
while (maxData >= p)
{
//p *= 10; // Maybe overflow
maxData /= 10;
++d;
}
return d;
}
void radixsort(int data[], int n)
{
int d = maxbit(data, n);
int *tmp = new int[n];
int *count = new int[10];
int i, j, k;
int radix = 1;
for(i = 1; i <= d; i++)
{
for(j = 0; j < 10; j++)
count[j] = 0;
for(j = 0; j < n; j++)
{
k = (data[j] / radix) % 10;
}
for(j = 1; j < 10; j++)
count[j] = count[j - 1] + count[j];
for(j = n - 1; j >= 0; j--)
{
k = (data[j] / radix) % 10;
tmp[count[k] - 1] = data[j];
count[k]--;
}
for(j = 0; j < n; j++)
data[j] = tmp[j];
radix = radix * 10;
}
delete []tmp;
delete []count;
}
堆排序
void max_heapify(int arr[], int start, int end) {
int dad = start;
int son = dad * 2 + 1;
while (son <= end) {
if (son + 1 <= end && arr[son] < arr[son + 1])
son++;
if (arr[dad] > arr[son])
return;
else {
swap(arr[dad], arr[son]);
dad = son;
son = dad * 2 + 1;
}
}
}
void heap_sort(int arr[], int len) {
for (int i = len / 2 - 1; i >= 0; i--)
max_heapify(arr, i, len - 1);
for (int i = len - 1; i > 0; i--) {
swap(arr[0], arr[i]);
max_heapify(arr, 0, i - 1);
}
}
另外还有桶排序,这里就不列举了。
差分与前缀和
差分与前缀和是相对的,差分的本质就是前缀和的逆运算,相比前缀和要抽象一点,这里不详细介绍前缀和
对于一个一维数组a,其差分数组b,两者关系有:
b[n]=a[n]−a[n−1];b[n]=a[n]-a[n-1];b[n]=a[n]−a[n−1];
对于一个二维数组a,其差分矩阵b,两者关系有:
b[i][j]=a[i][j]−a[i][j−1]−a[i−1][j]+a[i−1][j−1];b[i][j] = a[i][j] - a[i][j-1] - a[i-1][j] + a[i-1][j-1];b[i][j]=a[i][j]−a[i][j−1]−a[i−1][j]+a[i−1][j−1];
实现An中从(x1,y1)(x1,y1)(x1,y1)
到(x2,y2)(x2,y2)(x2,y2)
子矩阵同时加上或者减去常数C
通过对差分矩阵元素的的改变进而改变前缀和矩阵元素的值,代码实现形式:
b[x1][y1]+=c;b[x1][y1] += c;b[x1][y1]+=c;
b[x1][y2+1]−=c;b[x1][y2+1] -= c;b[x1][y2+1]−=c;
b[x2+1][y1]−=c;b[x2+1][y1] -= c;b[x2+1][y1]−=c;
b[x2+1][y2+1]+=c;b[x2+1][y2+1] += c;b[x2+1][y2+1]+=c;
对于一维数组a,其有差分数组b,若想在一维数组 a l 到 r 这个区间加上c,则对于差分数组b有:
b[l]+=c;b[l] += c;b[l]+=c;`
`b[r+1]−=c;b[r+1] -= c;b[r+1]−=c;
https://www.luogu.com.cn/problem/P3397
高精度算法
高精加
#include <iostream>
#include <tuple>
#include <algorithm>
#define LL long long
#define MAX 100000
using namespace std;
int a[MAX], b[MAX], c[MAX]{ 0 };
int main()
{
string num1, num2;
cin >> num1 >> num2;
for (int i = num1.size()-1,j=0; i >=0 ; i--,j++)
a[j] = num1[i] - '0';
for (int i = num2.size()-1,j=0; i >=0 ; i--,j++)
b[j] = num2[i] - '0';
int max = num1.size() > num2.size() ? num1.size() : num2.size();
for (int i = 0; i < max; i++)
{
c[i] += a[i] + b[i];
c[i + 1] = c[i] / 10;
c[i] %= 10;
}
max--;
while (c[max+1])
{
max++;
}
for (int i = max; i >=0; i--)
cout << c[i];
return 0;
}
高精乘
#include <iostream>
#include <tuple>
#include <algorithm>
#define LL long long
#define MAX 100000
using namespace std;
int a[MAX], b[MAX], c[MAX]{ 0 };
int main()
{
string num1, num2;
cin >> num1 >> num2;
for (int i = num1.size()-1,j=1; i >=0 ; i--,j++)
a[j] = num1[i] - '0';
for (int i = num2.size()-1,j=1; i >=0 ; i--,j++)
b[j] = num2[i] - '0';
int max = num1.size() > num2.size() ? num1.size() : num2.size();
for (int i = 1; i <= num1.size(); i++)
{
for (int j = 1; j <= num2.size(); j++)
{
c[j + i - 1] += a[i] * b[j];
}
}
int len = num1.size() + num2.size();
for (int i = 1; i <= len; i++)
{
c[i + 1] += c[i] / 10;
c[i] %= 10;
}
int cnt = 0;
for (int i = 1; i <= len; i++)
{
if (c[i] == 0)
cnt++;
}
if (cnt == len) cout << 0;
else
{
while (!c[len]) len--;
for (int i = len; i >= 1; i--)
cout << c[i];
}
return 0;
}
枚举
二进制枚举
int main()
{
int n;
cin >> n;
for(int i = 0; i < (1<<n); i++)
{
for(int j = 0; j < n; j++)
{
if(i & (1 << j))
cout<<j;
}
cout<<endl;
}
return 0;
}
动态规划
背包dp
01背包
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
if(j < v[i])
f[i][j] = f[i - 1][j];
else
f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
for(int i = 1; i <= n; i++)
for(int j = m; j >= v; j--)
f[j] = max(f[j], f[j - v] + w);
完全背包
可以无限拿取背包内的东西
for(int i = 1 ; i <= n ; i++)
for(int j = 0 ; j <= m ; j ++)
{
f[i][j] = f[i-1][j];
if(j-v[i]>=0)
f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);
}
for(int i = 1 ; i<=n ;i++)
for(int j = v[i] ; j<=m ;j++)//注意了,这里的j是从小到大枚举,和01背包不一样
{
f[j] = max(f[j],f[j-v[i]]+w[i]);
}
多重背包
将背包拆出来,然后转化成01背包问题即可
ll n,m;
ll cnt=1;
ll a,b,c;
for(ll i=1;i<=n;i++)
{
cin>>a>>b>>c;
for(ll j=1;j<=c;j++)
{
v[cnt]=a;
w[cnt]=b;
cnt++;
}
}
for(ll i=1;i<=cnt;i++)
for(ll j=m;j>=v[i];j--)
f[j]=max(f[j],f[j-v[i]]+w[i]);
如果数据范围较大可能会导致超时,此时需要进行优化
二进制优化
如果不采用动态规划的做法, 就像普通的遍历问题那样, 是否采用二进制的计数方法对时间复杂度的优化没有任何关系。
for (int i = 1; i <= n; i ++)
{
int a, b, s;
int k = 1;
while (k <= s)
{
cnt ++;
v[cnt] = a * k;
w[cnt] = b * k;
s -= k;
k *= 2;
}
if (s > 0)
{
cnt ++;
v[cnt] = a * s;
w[cnt] = b * s;
}
}
n = cnt;
混合背包
就是将上面三种混合起来,没什么好说的
for (循环物品种类) {
if (是 0 - 1 背包)
0 - 1 背包代码;
else if (是完全背包)
完全背包代码;
else if (是多重背包)
多重背包代码;
}
分组背包
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
f[i][j]=f[i-1][j];
for(int k=0;k<s[i];k++){
if(j>=v[i][k]) f[i][j]=max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);
}
}
}
单调队列优化
因为单调队列的单调性,所以我们可以用它来进行降低DP维数,进行时间和空间的优化。
这里以优化多重背包问题为例,
转移只会发生在「对当前物品体积取余相同」的状态之间。
for (int j = 0; j < vi; j++) {
int head = 0, tail = -1;
for (int k = j; k <= C; k+=vi) {
dp[k] = g[k];
// 将不在窗口范围内的值弹出
if (head <= tail && q[head] < k - si * vi) head++;
// 如果队列中存在元素,直接使用队头来更新
if (head <= tail) dp[k] = max(dp[k], g[q[head]] + (k - q[head]) / vi * wi);
// 当前值比对尾值更优,队尾元素没有存在必要,队尾出队
while (head <= tail && g[q[tail]] - (q[tail] - j) / vi * wi <= g[k] - (k - j) / vi * wi) tail--;
// 将新下标入队
q[++tail] = k;
}
}
区间dp
就是对区间进行动态规划
P1880 [NOI1995] 石子合并 - 洛谷
这题汇总了对于区间dp,求最大值,最小值以及形成一个圈的所有情况。
其中dp[j][j+i]dp[j][j+i]dp[j][j+i]
代表,以j为起点,j+i为终点,区间的最优解情况
状态转移方程:
dp1[j][j+i]=min/max(dp1[j][j+i],dp1[j][k]+dp1[k+1][j+i]+s[j+i]−s[j−1]);dp1[j][j+i]=min/max(dp1[j][j+i],dp1[j][k]+dp1[k+1][j+i]+s[j+i]-s[j-1]);dp1[j][j+i]=min/max(dp1[j][j+i],dp1[j][k]+dp1[k+1][j+i]+s[j+i]−s[j−1]);
s[j+i]−s[j−1]s[j+i]-s[j-1]s[j+i]−s[j−1]
代表j到j+i这个区间的所有元素的和,之所以要加上区间所有元素的和,是因为单纯将dp1[j][k],dp1[k+1][j+i]dp1[j][k],dp1[k+1][j+i]dp1[j][k],dp1[k+1][j+i]
相加,其实少加了一次j到j+i这个区间的所有元素的。
#include <bits/stdc++.h>
using namespace std;
int dp1[501][501],dp2[501][501],n,a[501],s[501];
int main() {
std::ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
a[n+i]=a[i];
}
memset(dp1,127,sizeof(dp1));
memset(dp2,0,sizeof(dp2));
n*=2;
for(int i=1;i<=n;i++)
s[i]=s[i-1]+a[i];
for(int i=1;i<=n;i++){
dp1[i][i]=0;
dp2[i][i]=0;
}
for(int i=1;i<n;i++)
for(int j=1;j<n-i;j++)
for(int k=j;k<j+i;k++){
dp1[j][j+i]=min(dp1[j][j+i],dp1[j][k]+dp1[k+1][j+i]+s[j+i]-s[j-1]);
dp2[j][j+i]=max(dp2[j][j+i],dp2[j][k]+dp2[k+1][j+i]+s[j+i]-s[j-1]);
}
int ans1=1<<30,ans2=0;
for(int i=1;i<=n;i++){
ans1=min(ans1,dp1[i][i+n/2-1]);
ans2=max(ans2,dp2[i][i+n/2-1]);
}
cout<<ans1<<endl;
cout<<ans2<<endl;
return 0;
}
树形dp
P1352 没有上司的舞会 - 洛谷
wls的板子,本题比起wls的题目,就是多了个·查找根节点的操作,其余的都是一样的,树形dp说白了就是在树结构在上进行dp,此时就需要遍历树(使用dfs或者bfs),wls的建链表(链式向前星)挺巧妙的,可以学学;
#include <bits/stdc++.h>
using namespace std;
const int MAX = 200005;
struct Node {
Node* next;
int Where;
}* f[MAX],a[MAX];
int v[MAX], n, l;
int vis[MAX];
long long dp[MAX][2];
inline void makelist(int x, int y) {//起点,终点
a[++l].Where = y;
a[l].next = f[x];
f[x] = &a[l];
vis[y] = 1;
}
inline void solve(int d) {
dp[d][1] = v[d];
for (Node* x=f[d]; x; x = x->next) {
solve(x->Where);
dp[d][0] += max(dp[x->Where][0], dp[x->Where][1]);
dp[d][1] += dp[x->Where][0];
}
}
int main() {
std::ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
int res = 0;
memset(f, 0, sizeof(f));
for (int i = 1; i <= n; i++)
cin >> v[i];
for (int i = 1; i <= n - 1; i++) {
int l,k; cin >> l>>k;
makelist(k,l);
}
int root; //寻根,谁的入度为0谁就是根
for (int i = 1; i <= n; i++) {
if (!vis[i]) {
root = i;
break;
}
}
solve(root);
cout << max(dp[root][1],dp[root][0]);
return 0;
}
状压dp
数位dp
搜索
DFS基本形式
/*
基本模型
void dfs(int step)
{
if(所有空都填完了){
判断最优解或者记录答案;
return;
{
for(枚举所有的情况){
if(该选项是合法的)
记录下这个空;
dfs(step+1);
取消这个空
}
}
*/
DFS基本运用:N皇后问题
#include <iostream>
using namespace std;
const int N = 1000;
int n,res;
int a[N], b[N], c[N];//行,两个斜行,对于某一个点而言,
//其中一个斜行x+y固定,另一个行x-y固定;
void dfs(int step)
{
if (step > n)
{
res++;
return;
//因为棋子已经摆完了,所以回溯到上一种情况继续摆放。
}
for (int i = 1; i <= n; i++)
{
if (a[i] == 0 && b[step + i] == 0 && c[step - i + 20] == 0)
{
a[i] = 1; b[step + i] = 1; c[step - i + 20] = 1;
dfs(step + 1);
a[i] = 0; b[step + i] = 0; c[step - i + 20] = 0;
//如果没有满足上述情况的位置放置,则恢复现场。
}
}
}
int main()
{
cin >> n;
dfs(1);
cout << res;
return 0;
}
BFS基本形式
通常是与队列(queue)捆绑的。
/*
Q.push(初始状态);
while(!Q.empty())
{
State u =Q.front();
Q.pop();
for(枚举所有的可能状态)
if(是合法的)
Q.push(v);
}
*/
BFS的基本应用:
P1443 马的遍历 - 洛谷
#include <bits/stdc++.h>
using namespace std;
const int N = 1000;
int horse[8][2] = {
{1,2},{2,1},{-1,2},{-2,1},{-1,-2},{-2,-1},{1,-2},{2,-1}
};
int ans[N][N];
queue<pair<int, int>> temp;
int main()
{
memset(ans, -1, sizeof(ans));
int n, m, dx, dy;
cin >> n >> m >> dx >> dy;
ans[dx][dy] = 0;
temp.push({ dx,dy });
while (!temp.empty())
{
pair<int, int> Top = temp.front();//标记原点
temp.pop();
for (int i = 0; i < 8; i++) {
int dx1 = Top.first + horse[i][0];
int dy1 = Top.second + horse[i][1];
if (dx1 < 1 || dx1 > n || dy1 < 1 || dy1 > m || ans[dx1][dy1] != -1)
continue;
int temp1 = ans[Top.first][Top.second];//继承原点的步数
ans[dx1][dy1] = temp1 + 1;
temp.push({ dx1,dy1 });
}
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
printf("%-5d", ans[i][j]);
printf("\n");
}
return 0;
}
字符串算法
这里就不写暴力匹配了。
KMP
const int N = 1e5;
int Next[N];
void getNext(string b,int next[]){//取模式串的next数组
int len = b.size();
next[0] = -1;
int k = -1, j = 0;
while (j < len - 1) {
if (k == -1 || b[j] == b[k]) {
k++;
j++;
if (b[j] != b[k]) next[j] = k;
else
next[j] = next[k];
}
else k = next[k];
}
}
int kmp_search(string a , string b) {
int i = 0, j = 0;
getNext(b, Next);
while (i < a.size()&&j<b.size()) {
if (j==-1||a[i] == b[j]) i++, j++;
else j = Next[j];//递归思想,将模式串下标返回Next[j]
}
if (j == b.size())
return i - j;
else
return -1;
}
EXkmp
next[i]: T[i]...T[m−1] next[i]: T[i]...T[m - 1]next[i]: T[i]...T[m−1]
与 T(模式串) 的最长相同前缀长度;
extend[i]: S[i]...S[n−1]extend[i]: S[i]...S[n - 1]extend[i]: S[i]...S[n−1]
与 T 的最长相同前缀长度。
void GetNext(string T, int m, int next[])
{
int a = 0, p = 0;
next[0] = m;
for (int i = 1; i < m; i++)
{
if (i >= p || i + next[i - a] >= p)
{
if (i >= p)
p = i;
while (p < m && T[p] == T[p - i])
p++;
next[i] = p - i;
a = i;
}
else
next[i] = next[i - a];
}
}
void GetExtend(string S, int n, string T, int m, int extend[], int next[])
{
int a = 0, p = 0;
GetNext(T, m, next);
for (int i = 0; i < n; i++)
{
if (i >= p || i + next[i - a] >= p)
{
if (i >= p)
p = i;
while (p < n && p - i < m && S[p] == T[p - i])
p++;
extend[i] = p - i;
a = i;
}
else
extend[i] = next[i - a];
}
}
BM算法
bm可以看作进阶的kmp算法,效率相比kmp要高。
BM算法定义了两个规则:
坏字符规则:当文本串中的某个字符跟模式串的某个字符不匹配时,我们称文本串中的这个失配字符为坏字符,此时模式串需要向右移动,移动的位数 = 坏字符在模式串中的位置 - 坏字符在模式串中最右出现的位置。此外,如果"坏字符"不包含在模式串之中,则最右出现位置为-1。
好后缀规则:当字符失配时,后移位数 = 好后缀在模式串中的位置 - 好后缀在模式串上一次出现的位置,且如果好后缀在模式串中没有再次出现,则为-1。
const int N = 1e4;
int BC[N],suffix[N],gs[N];
void getBC(string pattern, int bc[]) {//BC表
for (int i = 0; i < 256; i++)
bc[i] = -1;
for (int i = 0; i < pattern.size(); i++)
bc[pattern[i]] = i;
}
void suffixes(string a, int suffix[]) {//构建suffix表,suffix[i] = s 表示以i为边界,与模式串后缀匹配的最大长度。
int len = a.size(),num;
suffix[len - 1] = len;
for (int i = len - 2; i >= 0; i--) {
for (num = 0; num <= i && a[i - num] == a[len - num - 1]; num++)
suffix[i] = num;
}
}
void getGS(string a, int gs[]) {//构建gs表,记录了每次需要移动的距离。
int len = a.size(), lastindex = len - 1;
suffixes(a, suffix);
for (int i = 0; i < len; i++)
gs[i] = len;
for (int i = lastindex; i >= 0; i--) {
if (suffix[i] == i + 1) {
for (int j = 0; j < lastindex - i; j++) {
if (gs[j] == len)
gs[j] = lastindex - i;
}
}
}
for (int i = 0; i < lastindex; i++) {
gs[lastindex - suffix[i]] = lastindex - i;
}
}
int BMsearch(string a, string b ) {
memset(BC, 0, N);
memset(gs, 0, N);
getBC(b,BC);
getGS(b,gs);
int ptr1 = 0, ptr2=0;
int len1 = a.size(), len2 = b.size(), maxIndex = b.size() - 1;
while (ptr1 + len2 <= len1) {
for (ptr2 = maxIndex; ptr2 >= 0 && b[ptr2] == a[ptr1 + ptr2]; ptr2--);
if (ptr2 == -1) break;
else {
ptr1 += max(gs[ptr2], ptr2 - BC[a[ptr1 + ptr2]]);//好后缀和怀字符规则,取移动数目较大的。
}
}
return (ptr1 + len2 <= len1) ? ptr1 : -1;
}
最长回文子串
Manacher算法
查找一个字符串的最长回文子串,这里使用了string,推荐还是用char数组,string操作比较耗时。
string preProcess(string str) {//准备字符串
int len = str.size();
if (len == 0) return "^$";
string start = "^";
for (int i = 0; i < len; i++)
start =start + "#" + str[i];
start += "#$";
return start;
}
string manacher(string str) {//马拉车算法
string str1 = preProcess(str);
int len = str1.size();
int* arr = new int[len];
int C = 0, R = 0;
for (int i = 1; i < len - 1; i++) {
int i_mirror = 2 * C - i;
if (R > i) arr[i] = min(R - i, arr[i_mirror]);//防止超出R
else arr[i] = 0;//R=i的情况
while (str1[i + 1 + arr[i]] == str1[i - 1 - arr[i]]) arr[i]++;//中心拓展
if (i + arr[i] > R) {//更新边界
C = i;
R = i + arr[i];
}
}
int Max = 0, center = 0;
for (int i = 1; i < len - 1; i++) {
if (arr[i] > Max) {
Max = arr[i];
center = i;
}
}
int start = (center - Max) / 2;
return str.substr(start, Max);
}
java代码,没事写一个
class Solution {
public static String preString(String str){
int len=str.length();
if(len==0) return "^&";
String start="^";
for(int i=0;i<len;i++)
start=start+"#"+str.charAt(i);
start+="#&";
return start;
}
public static String manacher(String str){
String str1=preString(str);
int len=str1.length();
int[] arr=new int[len];
int C=0,R=0;
for(int i=1;i<len-1;i++){
int i_mirror=2*C-i;
if(R>i) arr[i]=Math.min(arr[i_mirror],R-i);
else arr[i]=0;
while(str1.charAt(i+1+arr[i])==str1.charAt(i-1-arr[i]))
arr[i]++;
if(arr[i]+i>R){
C=i;
R=i+arr[i];
}
}
int Max=0,center=0;
for(int i=1;i<len-1;i++){
if(arr[i]>Max) {
Max = arr[i];
center = i;
}
}
int beg=(center-Max)/2;
return str.substring(beg,beg+Max);
}
public String longestPalindrome(String s) {
return manacher(s);
}
}
dp实现
本质上就是枚举边界和长度,然后更新一下dp数组就好了
public static String dpSolve(String str){
int maxlen=1,start=0;
int len=str.length();
boolean[][] dp=new boolean[len][len];
for(int i=0;i<len;i++)
dp[i][i]=true;
for(int length=2;length<=len;length++) {
for (int i = 0; i < len; i++) {
int j = length + i - 1;
if(j>=len)
break;
if (str.charAt(i) != str.charAt(j))
dp[i][j] = false;
else {
if (j - i < 3)
dp[i][j] = true;
else
dp[i][j] = dp[i + 1][j - 1];
}
if (dp[i][j] && j - i + 1 > maxlen) {
maxlen = j - i + 1;
start = i;
}
}
}
return str.substring(start,start+maxlen);
}
数论
埃氏筛
就是从质数2开始,其整数倍均不为质数,将区间内所有的2的倍数的下标,标记为false,代表不为质数,再从第二个质数开始,将其倍数标记为false,以此循环,最后留下来的都是质数。
const int MAX = 1e5+1;
int isPrime[MAX];
int Prime[MAX];
void solve(int n) {
int n,ptr=0; cin >> n;
for (int i = 0; i <= n; i++)
isPrime[i] = 1;
isPrime[0] = isPrime[1] = false;
for (int i = 2; i <= n; i++) {
if (isPrime[i]) {
Prime[ptr++] = i;
for (int j = 2 * i; j <= n; j += i)
isPrime[j] = 0;
}
}
for (int i = 0; i < ptr; i++)
cout << Prime[i] << endl;
}
欧拉筛
核心思想是让每一个合数被其最小质因数筛到
关键就在于 if(i%isprime[j]==0)
#include <iostream>
using namespace std;
const int Max = 1e8 + 5;
int n, numPrime;
int prime[Max];
bool isPrime[Max];
int main()
{
std::ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
for (int i = 2; i <= n; i++) {
if (!isPrime[i])
prime[numPrime++] = i;
for (int j = 0; j < numPrime; j++) {
if (i * prime[j] > n)
break;
isPrime[i * prime[j]] = true;
if (i % prime[j] == 0)
break;
}
}
cout << numPrime;
return 0;
}
快速幂
快速幂本质上就是一种分治算法,有递归和迭代两个版本
递归
double quickMul(double x, long long N) {
if (N == 0) {
return 1.0;
}
double y = quickMul(x, N / 2);
return N % 2 == 0 ? y * y : y * y * x;
}
迭代
double quickMul(double x, long long N) {
double ans = 1.0;
// 贡献的初始值为 x
double x_contribute = x;
// 在对 N 进行二进制拆分的同时计算答案
while (N > 0) {
if (N % 2 == 1) {
// 如果 N 二进制表示的最低位为 1,那么需要计入贡献
ans *= x_contribute;
}
// 将贡献不断地平方
x_contribute *= x_contribute;
// 舍弃 N 二进制表示的最低位,这样我们每次只要判断最低位即可
N /= 2;
}
return ans;
}
数据结构
单调栈
保证栈中元素为单调递减或者递增的数据结构
stack<int> S;
int T; cin >> T;
while (T--) {
int x; cin >> x;
while (!S.empty() && S.top() > x)
S.pop();
S.push(x);
}
while (!S.empty()) {
int temp = S.top();
S.pop();
cout << temp<<" ";
}
模拟单调栈
typedef unsigned long long ull;
ull n,top,ans,c[2000001],s[2000001];
//top 栈首
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%llu",&c[i]);
for(int i=1;i<=n;i++){
while(top&&c[i]>=c[s[top]]){
ans^=s[top];
top--;
}
ans^=i;
s[++top]=i;
printf("%llu\n",ans);
}
return 0;
}
单调队列
保证队列中的元素单调递增或者单调递减
deque<int> Q;
for (int i = 0; i < n; ++i)
{
if (!Q.empty() && i - Q.front() >= m)
Q.pop_front();
while (!Q.empty() && V[Q.back()] < V[i])
Q.pop_back();
Q.push_back(i);
if (i >= m - 1)
cout << V[Q.front()] << " ";
}
模拟单调队列
ll a[MAX],q[MAX],front=1,rear=0;//front队首,rear队尾
for(int i=1;i<=n;i++)
{
while(front<=rear&&a[q[rear]]>=a[i])
rear--;
q[++rear]=i;
if(q[front]<i-k+1)
front++;
if(i>=k)
cout<<a[q[front]]<<" ";
}
cout<<endl;
for(int i=1;i<=n;i++){
while(front<=rear&&a[q[rear]]<=a[i])
rear--;
q[++rear]=i;
if(q[front]<i-k+1)
front++;
if(i>=k)
cout<<a[q[front]]<<" ";
}
字典树
数组存储
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
//Trie
const int MAX = 1e6 + 10;
const int charsize = 27;
//记录此节点的子节点编号(默认为小写字母)
int nxt[MAX][charsize];
//是否为终止节点
bool isend[MAX];
//cnt保存当前节点编号数
int root = 0, cnt = 0;
void insert(char s[], int len) {
int now = 0;
for (int i = 1; i <= len; i++) {
int x = s[i] - 1;
if (!nxt[now][x])
nxt[now][x] = ++cnt;
now = nxt[now][x];
}
isend[now] = 1;
}
bool search(char s[], int len) {
int now = 0;
for (int i = 1; i <= len; i++) {
int x = s[i] - 'a';
if (!nxt[now][x])
return false;
now = nxt[now][x];
}
return isend[now];
}
int main() {
std::ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
return 0;
}
结构体写法
//正常
struct TreeNode1 {
int nxt[charsize];
bool isend;
}tree1[MAX];
//字符集较大
struct TreeNode2 {
unordered_map<char, int> nxt_map;
bool isend;
}tree2[MAX];
//指针写法
struct TreeNode3 {
TreeNode3* nxt[charsize];
bool isend;
}tree3[MAX];//insert search的操作思路类似,不写了。
统计前缀
把isend数组改成tol数组记录一下以某个位置为重点的字符串为前缀的数量就可以了。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int charsize = 70;
const int MAXN = 3e6 + 10;
int nxt[MAXN][charsize],root,cnt;
int tol[MAXN];
int getnum(char x) {
if (x >= 'A' && x <= 'Z')
return x - 'A';
else if (x >= 'a' && x <= 'z')
return x - 'a' + 26;
else
return x - '0' + 52;
}
void init(int n) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < charsize; j++) {
nxt[i][j] = 0;
}
}
for (int i = 0; i < n; i++)
tol[i] = 0;
}
int main() {
std::ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int T; cin >> T;
while (T--) {
init(cnt + 1);
root = 0, cnt = 0;
int n, q; cin >> n >> q;
while(n--){
string s; cin >> s;
int now = 0;
for (int i = 0; i < s.size(); i++) {
int x = getnum(s[i]);
if (!nxt[now][x])
nxt[now][x] = ++cnt;
now = nxt[now][x];
tol[now]++;
}
}
while(q--) {
string pat; cin >> pat;
int now = 0,flag=1;
for (int i = 0; i < pat.size(); i++) {
int x = getnum(pat[i]);
if (!nxt[now][x]) {
flag = 0;
cout << 0 << endl;
break;
}
now = nxt[now][x];
}
if(flag)
cout << tol[now] << endl;
}
}
return 0;
}
链表
单链
struct link
{
int data;
link* next;
link* last;
} ;
link* creatlink()//创建一个头结点,链表的起始位置。
{
link* headNode = (link*)malloc(sizeof(link));
if (headNode == NULL)
exit(1);
headNode->next = NULL;
headNode->last = NULL;
return headNode;
}
link* creatNode(int data)//创建一个结点,后续将其插入链表。
{
link* NewNode = (link*)malloc(sizeof(link));
if (NewNode == NULL)
exit(1);
NewNode->data = data;
NewNode->next = NULL;
NewNode->last = NULL;
return NewNode;
}
void addNode(link * headNode,int data)//为该链表添加一个结点
{
link* AddNode = creatNode(data);
AddNode->next = headNode->next;
headNode->next = AddNode;
}
void delNode(link* headNode, int num)//删除某个节点
{
link* pmove = headNode->next;
link* p1move = headNode; if(p1move->data==num){ headNode->next=NULL; headNode=pmove; }
while (pmove)
{
if (pmove->data == num)
{
p1move->next = pmove->next;
}
pmove=pmove->next;
p1move=p1move->next;
}
}
void insertNode(link* headNode, int num,int data) {
link* pmove = headNode->next;
link* p1move = headNode;
link* node = creatNode(data);
while (pmove)
{
if (pmove->data == num)
{
node->next = pmove->next;
pmove->next = node;
break;
}
pmove = pmove->next;
p1move = p1move->next;
}
}
int printflink(link* headNode)//打印节点数据
{
link* ptrmove = headNode->next;
while (ptrmove)
{
printf("%d\n", ptrmove->data);
ptrmove =ptrmove->next;
}
return 0;
}
void lastaddNode(link* headNode, int num)//在链表末尾添加一个新的节点
{
link* Node1 = lastNode(headNode);
link* AddNode = creatNode(num);
Node1->next = AddNode;
}
link* lastNode(link* headNode)//使当前指针指到链表最后一个节点。
{
while (headNode->next)
{
headNode = headNode->next;
}
return headNode;
}
双链
双链只需要在数据结构上附加一个域,包含指向前一个单元的指针即可。
Floyd 判圈算法
判断是否有环
定义两个指针p1与p2,起始时,都指向链表的起点A,p1每次移动1个长度,p2每次移动2个长度。如果p2在移到链表的尾端时,并未与p1相遇,表明链表中不存在环。如果p1与p2相遇在环上的某一点C,表明链表有环。
环的长度
将指针p1固定在相遇位置C,移动p2,每次移动1个长度,并用变量计数。当p2再次与p1相遇时,此时该变量的值就是环的长度。
环的起点
将指针p1指向链表的起始位置A,指针p2仍在位置C,指针p1与p2每次均移动一个单位,p1与p2再次相遇的位置就是环的起点位置点B。
//判断链表中是否有环
struct Node
{
int data;
Node* next;
};
bool findCircle(Node* list) {
if (list == NULL || list->next == NULL)
return false;
Node* ptr_slow = list, *ptr_fast = list;
while (ptr_fast != ptr_slow) {
if (ptr_fast == NULL || ptr_fast->next == NULL)
return false;
ptr_slow = ptr_slow->next;
ptr_fast = ptr_fast->next->next;
}
return true;
}
//寻找环的起点
Node *detectCycle(Node *head) {
Node *ptr_slow = head, *ptr_fast = head;
while (ptr_fast != NULL) {
ptr_slow = ptr_slow->next;
if (ptr_fast->ptr_next == NULL)
return NULL;
ptr_fast = ptr_fast->next->next;
if (ptr_fast == ptr_slow) {
Node *ptr = head;
while (ptr != ptr_slow) {
ptr = ptr->next;
ptr_slow = ptr_slow->next;
}
return ptr;
}
}
return NULL;
}
树
二叉查找树
struct DoubleTree
{
int data;
DoubleTree* lchild;
DoubleTree* rchild;
};
DoubleTree* creatHead(int num) {
DoubleTree* Tree = new DoubleTree;
Tree->data = num;
Tree->lchild = NULL;
Tree->rchild = NULL;
return Tree;
}
DoubleTree* MakeEmpty(DoubleTree* Tree) {
if (Tree != NULL) {
MakeEmpty(Tree->lchild);
MakeEmpty(Tree->rchild);
free(Tree);
}
return NULL;
}
DoubleTree* Find(DoubleTree* Tree, int Key) {
if (Tree == NULL) return NULL;
if (Key < Tree->data)
return Find(Tree->lchild, Key);
else if (Key > Tree->data)
return Find(Tree->rchild, Key);
else
return Tree;
}
DoubleTree* FindMin(DoubleTree* Tree) {
if (Tree == NULL) return NULL;
else if (Tree->lchild == NULL) return Tree;
else return FindMin(Tree->lchild);
}
DoubleTree* FindMax(DoubleTree* Tree) {
if (Tree == NULL) return NULL;
else if (Tree->rchild == NULL) return Tree;
else return FindMax(Tree->rchild);
}
DoubleTree* insert(DoubleTree* Tree, int x) {
if (Tree == NULL) {
Tree = new DoubleTree;
if (Tree == NULL) exit(1);
else {
Tree->data = x;
Tree->lchild = NULL;
Tree->rchild = NULL;
}
}
else if (x < Tree->data)
Tree->lchild = insert(Tree->lchild, x);
else if (x > Tree->data)
Tree->rchild = insert(Tree->rchild, x);
return Tree;
}
DoubleTree* DeleteNode(DoubleTree* Tree, int x) {
DoubleTree* temp;
if (Tree == NULL) exit(1);
else if ((x < Tree->data))
Tree->lchild = DeleteNode(Tree->lchild, x);
else if ((x > Tree->data))
Tree->rchild = DeleteNode(Tree->rchild, x);
else if (Tree->lchild && Tree->rchild) {
temp = FindMin(Tree->rchild);
Tree->data = temp->data;
Tree->rchild = DeleteNode(Tree->rchild, Tree->data);
}
else {
temp = Tree;
if (Tree->lchild == NULL) Tree = Tree->rchild;
else if (Tree->rchild == NULL) Tree = Tree->lchild;
free(temp);
}
return Tree;
}
void print(DoubleTree* Tree) {
if (Tree == NULL)
return;
cout << Tree->data << " ";
print(Tree->lchild);
print(Tree->rchild);
}
AVL树(平衡二叉树)
AVLtree* SingerotateWihtleft(AVLtree* T) {//左左
AVLtree* T1;
T1 = T->lchild;
T->lchild = T1->rchild;
T1->rchild = T;
T->height = max(returnHeight(T->lchild), returnHeight(T->rchild)) + 1;
T1->height = max(returnHeight(T1->lchild), T->height) + 1;
return T1;
}
AVLtree* SingerotateWihtright(AVLtree* T) {//右右
AVLtree* T1;
T1 = T->rchild;
T->rchild = T1->lchild;
T1->lchild = T;
T->height = max(returnHeight(T->lchild), returnHeight(T->rchild)) + 1;
T1->height = max(T->height, returnHeight(T1->rchild)) + 1;
return T1;
}
AVLtree* DoublerotateWihtleft(AVLtree* T) {//左右
T->lchild = SingerotateWihtright(T->lchild);
return SingerotateWihtleft(T);
}
AVLtree* DoublerotateWihtright(AVLtree* T) {//右坐
T->rchild = SingerotateWihtleft(T->rchild);
return SingerotateWihtright(T);
}
AVLtree* insertNode(AVLtree* T,int x) {
if (T == NULL) {
T = new AVLtree;
if (T == NULL) exit(1);
else {
T->data = x; T->height = 0;
T->lchild = NULL; T->rchild = NULL;
}
}
else if (x < T->data) {
T->lchild = insertNode(T->lchild, x);
if (returnHeight(T->lchild) - returnHeight(T->rchild) == 2) {
if (x < T->lchild->data)
T = SingerotateWihtleft(T);
else
T = DoublerotateWihtleft(T);
}
}
else if (x > T->data) {
T->rchild = insertNode(T->rchild, x);
if (returnHeight(T->rchild) - returnHeight(T->lchild) == 2) {
if (x > T->rchild->data)
T = SingerotateWihtright(T);
else
T = DoublerotateWihtright(T);
}
}
T->height = max(returnHeight(T->lchild), returnHeight(T->rchild))+1;
return T;
}
AVLtree* DeleteNode(AVLtree* T, int num) {//太麻烦了,删除操作相对较少懒惰删除是最好的策略
AVLtree* temp = NULL;
if (T == NULL) cout << "NO data" << endl;
else if (num < T->data)
T->lchild = DeleteNode(T->lchild, num);
else if (num > T->data)
T->rchild = DeleteNode(T->rchild, num);
else if (T->lchild && T->rchild) {
temp = FindMin(T);
T->data = temp->data;
T->rchild = DeleteNode(T->rchild, T->data);
}
else {
temp = T;
if (T->lchild == NULL)
T = T->rchild;
else if (T->rchild == NULL)
T = T->lchild;
free(temp);
}
if (T!=NULL) {
int lheight = returnHeight(T->lchild), rheight = returnHeight(T->rchild);
T->height = (lheight > rheight ? lheight : rheight)+1;
if (lheight - rheight == 2) {
if (returnHeight(T->lchild->lchild) >= returnHeight(T->lchild->rchild))
T = SingerotateWihtleft(T);
else
T = DoublerotateWihtleft(T);
}
else if (rheight - lheight == 2) {
if (returnHeight(T->rchild->rchild) >= returnHeight(T->rchild->lchild))
T = SingerotateWihtright(T);
else
T = DoublerotateWihtright(T);
}
}
return T;
}
树状数组
树状数组的基本实现
时间复杂度为O(logN)
lowbit(x) = x&(-x); (说简单点就是x的二进制数从左往右数第一个1的位数,比如100的lowbit运算结果为3)
功能(在O(logN)的时间复杂度下)
单点加
查询前缀和
P3374 【模板】树状数组 1 - 洛谷 |
const int MAX = 5e5 + 5;
int a[MAX], c[MAX],n;
int query(int x) { //查询1-x的和
int s = 0;
for (; x; x -= x & (-x))
s += c[x];
return s;
}
void modify(int x, long long k) {//修改树状数组
for (; x <= n; x += x & (-x) )
c[x]+=k;
}
int main() {
int m; cin >> n >> m;
for (int i = 1; i <= n; i++) {
int num; cin >> num;
modify(i, num);
}
while (m--) {
int choice,x,y; cin >> choice;
if (choice == 1) {
cin >> x >> y;
modify(x, y);
}
else {
cin >> x >> y;
cout << query(y) - query(x-1) << endl; //计算x---y的和
}
}
return 0;
}
区间加
对于数组a,区间[l,r]加上k,则对于其差分数组d(对于ai,ai=d1到di之和),dl+=k,dr-=k;
这里就不多赘述了,与上述模板相比,就是将树状数组c的初始值初始化为a的差分数组,然后再修改值的时候,加上一个modify(r+1,-k);
P3368 【模板】树状数组 2 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include <bits/stdc++.h>
using namespace std;
const int MAX = 5e5 + 5;
int c[MAX], n;
int query(int x) { //查询1-x的和
int s = 0;
for (; x; x -= x & (-x))
s += c[x];
return s;
}
void modify(int x, long long k) {//修改树状数组
for (; x <= n; x += x & (-x))
c[x] += k;
}
int main() {
int m; cin >> n >> m;
int last = 0;
for (int i = 1; i <= n; i++) {
int now; cin >> now;
modify(i, now-last);
last = now;
}
while (m--) {
int choice, x, y, k ; cin >> choice;
if (choice == 1) {
cin >> x >> y>> k ;
modify(x, k);
modify(y + 1, -k);
}
else {
cin >> x;
cout << query(x) << endl;
}
}
return 0;
}
一维树状数组基本应用
P1908 逆序对
对pair数组的first进行排序,这里的first表示值,second表示下标。后面在创建一个数组A,从pair数组的第一个值开始,使得A[pair[i].second]=i,最后可以使得数组A和原数组,数据大小是相反对应的,比如在下标3处,原数组存储的是最大值,A数组存储的则是最小值1。
最后将A数组,从第一个数开始,ans+=query(A[i]),再modify(A[i],1),这一操作,表示ans+=(以i为分界点,满足j< i,且Aj< Ai的元素个数),直到最后一个元素,最后ans就是最后的结果(因为答案要求的是j< i,且Aj>Ai,是相反的,正好A数组和原数组数据大小是相反对应的,所以最后的结果是正确的)
#include <bits/stdc++.h>
using namespace std;
const int MAX = 5e5 + 5;
typedef long long ll;
typedef pair<ll, ll> Pll;
int n, a[MAX], c[MAX];
Pll temp[MAX];
class cmp {
public:
bool operator()(const Pll& p1, const Pll& p2) {
if (p1.first > p2.first)
return true;
else if (p1.first == p2.first && p1.second > p2.second)
return true;
else
return false;
}
};
ll query(int x) {
ll s = 0;
for (; x; x -= x & (-x))
s += c[x];
return s;
}
void modify(int x, int d) {
for (; x <= n; x += x & (-x))
c[x] += d;
}
int main() {
cin >> n;
ll ans = 0;
for (int i = 1; i <= n; i++) {
cin >> temp[i].first;
temp[i].second = i;
}
sort(temp + 1, temp + 1 + n, cmp());
for (int i = 1; i <= n; i++)
a[temp[i].second] = i;
for (int i = 1; i <= n; i++) {
ans += query(a[i]);
modify(a[i], 1);
}
cout << ans << endl;
return 0;
}
高维树状数组
这里以二维树状数组为例:
在一维树状数组中,tree[x](树状数组中的那个“数组”)记录的是右端点为x、长度为lowbit(x)的区间的区间和。
那么在二维树状数组中,可以类似地定义tree[x][y]记录的是右下角为(x, y),高为lowbit(x), 宽为 lowbit(y)的区间的区间和。
时间复杂度为 O(log2N)
基本实现和一位数状数组相差不多,拓展到k维树状数组就是嵌套k维数组,k 层for循环。
typedef long long ll;
const int MAX = 5 - 1;
ll c[MAX][MAX], n;
ll query(int x) {
ll s = 0;
for (int p=x; p; p -= p & (-p))
for(int q=x;q;q-=q&(-q))
s += c[p][q];
return s;
}
void modify(int x, ll d) {
for (int p = x; p <= n; p += p & (-p))
for (int q = x; q <= n; q += q & (-q))
c[p][q] += d;
}
线段树
区间查询最小值
单点修改
区间查询(树状数组仅可以进行前缀和查询)
时间复杂度O(logN),可采用数组实现。
线段树可以进行多种查询,以下的板子为查询区间内的最小值。
#include <bits/stdc++.h>
using namespace std;
const int MAX = 2e5 + 5;
int a[MAX];
struct Node {
int minv;
}tree[4*MAX];
//更新数据
void update(int id) {
tree[id].minv = min(tree[id * 2].minv, tree[id * 2 + 1].minv);
}
//建树
void build(int id, int l, int r) {
if (l == r) {
tree[id].minv = a[l];
return;
}
else {
int mid = (l + r) / 2;
build(id * 2, l, mid);
build(id * 2+1, mid+1, r);
update(id);
}
}
//节点为id,将区间[l,r]的数据,a[pos]修改为val
void change(int id, int l, int r, int pos, int val) {
if (l == r) {
tree[id].minv = val;
return;
}
else {
int mid = (l + r) / 2;
if (pos <= mid)
change(id * 2, l, mid, pos, val);
else
change(id * 2 + 1, mid + 1, r, pos, val);
update(id);
}
}
//[dl,dr]表示要查询的区间
int query(int id, int l, int r, int dl, int dr) {
if (l == dl && r == dr)
return tree[id].minv;
int mid = (l + r) / 2;
//[l,mid],[mid+1,r];
if (dr <= mid)
return query(2 * id, l, mid, dl, dr);
else if (dl > mid)
return query(2 * id + 1, mid + 1, r, dl, dr);
else {
// dr> mid, dl<=mid
//[ql,mid],[mid+1,dr];
return min(query(id * 2, l, mid, dl, mid), query(id * 2 + 1, mid + 1, r, mid+1, dr));
}
}
int main() {
// 3 2 4 6 8 9 5 3
int n; cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
build(1, 1, n);
change(1, 1, n, 2, 99);
cout << query(1, 1, n, 2, n - 1) << endl;
return 0;
}
区间查询最大字段和
高度封装板子,wls真是永远的神
#include <bits/stdc++.h>
using namespace std;
const int MAX = 2e5 + 5;
int a[MAX];
struct info {
int mPrefix,mSuffix,mSum,s;
info(){}
info(int a):mPrefix(a),mSuffix(a),mSum(a),s(a){}
};
info operator+(const info& left, const info& right) {
info a;
a.mSum = max({ left.mSum, right.mSum, left.mSuffix + right.mPrefix });
a.mPrefix = max(left.mPrefix, left.s + right.mPrefix);
a.mSuffix = max(right.mSuffix, right.s + left.mSuffix);
a.s = left.s + right.s;
return a;
}
struct Node {
info val;
}tree[4 * MAX];
//更新数据
void update(int id) {
tree[id].val = tree[id * 2].val + tree[id * 2 + 1].val;
}
//建树
void build(int id, int l, int r) {
if (l == r) {
tree[id].val = info(a[l]);
return;
}
else {
int mid = (l + r) / 2;
build(id * 2, l, mid);
build(id * 2+1, mid+1, r);
update(id);
}
}
//节点为id,将区间[l,r]的数据,a[pos]修改为val
void change(int id, int l, int r, int pos, int val) {
if (l == r) {
tree[id].val = info(val);
return;
}
else {
int mid = (l + r) / 2;
if (pos <= mid)
change(id * 2, l, mid, pos, val);
else
change(id * 2 + 1, mid + 1, r, pos, val);
update(id);
}
}
//[dl,dr]表示要查询的区间
info query(int id, int l, int r, int dl, int dr) {
if (l == dl && r == dr)
return tree[id].val;
int mid = (l + r) / 2;
//[l,mid],[mid+1,r];
if (dr <= mid)
return query(2 * id, l, mid, dl, dr);
else if (dl > mid)
return query(2 * id + 1, mid + 1, r, dl, dr);
else {
// dr> mid, dl<=mid
//[ql,mid],[mid+1,dr];
return query(id * 2, l, mid, dl, mid)+query(id * 2 + 1, mid + 1, r, mid+1, dr);
}
}
int main() {
/*
5 5
-1 2 -3 4 -5
2 4 5
1 2 4
2 1 5
1 4 -1
2 2 4
*/
std::ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n,q; cin >> n>>q;
for (int i = 1; i <= n; i++)
cin >> a[i];
build(1, 1, n);
for (int i = 0; i < q; i++) {
int ty; cin >> ty;
if (ty == 1) {
int x, d; cin >> x >> d;
change(1, 1, n, x, d);
}
else {
int l, r; cin >> l >> r;
auto ans = query(1, 1, n, l, r);
cout << ans.mSum << endl;
}
}
return 0;
}
线段树打标记
用于线段树区间修改
打标记:
懒标记:懒标记的作用是记录每次、每个节点要更新的值
标记永久化:将标记永久记录( 不建议)
标记下传:因为线段树分成了一个一个区间,所以可以将某个节点的懒标记,下传给他的子结点,并且将当前节点的懒标记置为0。
标记合并:将之前存在的相同节点的多个标记合并
更新信息
查询区间最大值
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX = 2e5 + 5;
ll a[MAX];
struct info {
ll maxv;
info() {}
info(ll x):maxv(x) {}
};
struct tag {
ll add;
tag(){}
tag(ll x) :add(x) {}
};
info operator+(const info& left, const info& right) {
return info(max(left.maxv, right.maxv));
}
info operator+(const info& val, const tag& t) {
return info(val.maxv+t.add);
}
tag operator+(const tag& t1, const tag& t2) {
return tag(t1.add+t2.add);
}
struct Node {
tag t;
info val;
}tree[4 * MAX];
//更新数据
void update(int id) {
tree[id].val = tree[id * 2].val + tree[id * 2 + 1].val;
}
//设置懒标记
void settag(int id,tag t) {
tree[id].val = tree[id].val + t;
tree[id].t = tree[id].t + t;
}
//下传操作
void pushDown(int id) {
if (tree[id].t.add != 0) {
settag(id * 2, tree[id].t);
settag(id * 2+1, tree[id].t);
tree[id].t = 0;
}
}
//建树
void build(int id, int l, int r) {
if (l == r) {
tree[id].val = info(a[l]);
return;
}
else {
int mid = (l + r) / 2;
build(id * 2, l, mid);
build(id * 2 + 1, mid + 1, r);
update(id);
}
}
//节点为id,将区间[l,r]的数据,a[pos]修改为val
void modify(int id, int l, int r, int dl,int dr,tag t) {
if (l == dl && r == dr) {
settag(id, t);
return;
}
int mid = (l + r) / 2;
//[l,mid],[mid+1,r];
pushDown(id);
if (dr <= mid)
modify(2 * id, l, mid, dl, dr,t);
else if (dl > mid)
modify(2 * id + 1, mid + 1, r, dl, dr,t);
else {
// dr> mid, dl<=mid
//[ql,mid],[mid+1,dr];
modify(id * 2, l, mid, dl, mid,t);
modify(id * 2 + 1, mid + 1, r, mid + 1, dr,t);
}
update(id);
}
//[dl,dr]表示要查询的区间
info query(int id, int l, int r, int dl, int dr) {
if (l == dl && r == dr)
return tree[id].val;
int mid = (l + r) / 2;
//[l,mid],[mid+1,r];
pushDown(id);
if (dr <= mid)
return query(2 * id, l, mid, dl, dr);
else if (dl > mid)
return query(2 * id + 1, mid + 1, r, dl, dr);
else {
// dr> mid, dl<=mid
//[ql,mid],[mid+1,dr];
return query(id * 2, l, mid, dl, mid) + query(id * 2 + 1, mid + 1, r, mid + 1, dr);
}
}
int main() {
/*
5 5
1 2 3 4 5
2 4 5
1 1 5 1
2 1 4
1 2 3 10
2 2 4
*/
std::ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, q; cin >> n >> q;
for (int i = 1; i <= n; i++)
cin >> a[i];
build(1, 1, n);
for (int i = 0; i < q; i++) {
int ty; cin >> ty;
if (ty == 1) {
int l,r, d; cin >> l>>r >> d;
modify(1, 1, n, l, r,tag(d));
}
else {
int l, r; cin >> l >> r;
auto ans = query(1, 1, n, l, r);
cout << ans.maxv << endl;
}
}
return 0;
}
查询区间和
可以进行区间乘和加某个数的操作
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX = 1e5 + 5;
ll a[MAX],mod;
struct Node {
ll val = 0, size = 0;
ll mul = 1, add = 0;
}tree[4 * MAX];
//更新数据
void update(int id) {
tree[id].val = (tree[id * 2].val + tree[id * 2 + 1].val)%mod;
}
//设置懒标记
void settag(int id,ll add,ll mul) {
tree[id].mul = (tree[id].mul * mul)%mod;
tree[id].add = (tree[id].add*mul+add)%mod;
tree[id].val = (tree[id].val*mul+tree[id].size*add)%mod ;
}
//下传操作
void pushDown(int id) {
if (tree[id].add != 0 || tree[id].mul != 1) {
settag(id * 2, tree[id].add,tree[id].mul);
settag(id * 2+1, tree[id].add,tree[id].mul);
tree[id].add = 0;
tree[id].mul = 1;
}
}
//建树
void build(int id, int l, int r) {
tree[id].size = r - l + 1;
if (l == r) {
tree[id].val = a[l];
return;
}
else {
int mid = (l + r) / 2;
build(id * 2, l, mid);
build(id * 2 + 1, mid + 1, r);
update(id);
}
}
//节点为id,将区间[l,r]的数据,增加t
void modify(int id, int l, int r, int dl,int dr,ll add,ll mul) {
if (dl == l && r == dr) {
settag(id, add,mul);
return;
}
pushDown(id);
int mid = (l + r) / 2;
//[l,mid],[mid+1,r];
if (dr <= mid)
modify(2 * id, l, mid, dl, dr,add,mul);
else if (dl > mid)
modify(2 * id + 1, mid + 1, r, dl, dr,add,mul);
else {
// dr> mid, dl<=mid
//[ql,mid],[mid+1,dr];
modify(id * 2, l, mid, dl, mid,add,mul);
modify(id * 2 + 1, mid + 1, r, mid + 1, dr, add, mul);
}
update(id);
}
//[dl,dr]表示要查询的区间
ll query(int id, int l, int r, int dl, int dr) {
if (dl == l && r == dr)
return tree[id].val;
pushDown(id);
ll mid = (l + r) / 2;
//[l,mid],[mid+1,r];
if (dr <=mid)
return query(2 * id, l, mid, dl, dr);
else if (dl > mid)
return query(2 * id + 1, mid + 1, r, dl, dr);
else {
// dr> mid, dl<=mid
//[ql,mid],[mid+1,dr];
return (query(id * 2, l, mid, dl, mid) + query(id * 2 + 1, mid + 1, r, mid + 1, dr))%mod;
}
}
int main() {
std::ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, q; cin >> n >> q>>mod;
for (int i = 1; i <= n; i++)
cin >> a[i];
build(1, 1, n);
for (int i = 0; i < q; i++) {
int ty; cin >> ty;
if (ty == 1) {
int l,r, k; cin >> l >> r >> k;
modify(1, 1, n, l, r, 0, k);
}
else if(ty==2)
{
int l, r, d; cin >> l >> r >> d;
modify(1, 1, n, l, r, d, 1);
}
else {
int l, r; cin >> l >> r;
auto ans = query(1, 1, n, l, r);
cout << ans << endl;
}
}
return 0;
}
优先队列(堆)
初始化
struct heapStruct {
int Capacity;//规模
int Size;//数据数量
int *data;
};
heapStruct* Init(int MaxData) {
heapStruct* H;
H = new heapStruct;
if (H == NULL) exit(1);
H->data = new int[(MaxData + 1)];
if (H->data == NULL) exit(1);
H->Capacity = MaxData;
H->Size = 0;
H->data[0] = INT_MIN;
return H;
}
插入
void insert(int x, heapStruct* H) {//
int i;
for (i = ++H->Size; H->data[i / 2] > x; i /= 2)//上滤操作,直到找到正确位置
H->data[i] = H->data[i / 2];
H->data[i] = x;
}
删除最小元
int DeleteMin(heapStruct* H) {
int i, child;
int MinData, LastData;
MinData = H->data[1];
LastData = H->data[H->Size--];
for (int i = 1; i * 2 <= H->Size; i = child) {//下虑操作
child = i * 2;
if (child != H->Size && H->data[child + 1] < H->data[child]) child++;
if (LastData > H->data[child]) H->data[i] = H->data[child];
else
break;
}
H->data[i] = LastData;
return MinData;
}
哈曼夫树
哈夫曼树指的就是,WPL最小的二叉树,最优二叉树。通过构建哈夫曼树,我们可以很容易的求出最小WPL。我们可以采用优先队列(堆)的方式,求出最小WPL。
P1090 [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G - 洛谷
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <queue>
using namespace std;
int main()
{
priority_queue<int,vector<int>,greater<int>> h;
int n,res=0; cin >> n;
for (int i = 0; i < n; i++)
{
int num;
cin >> num;
h.push(num);
}
while (h.size()>1)
{
int sum = 0;
sum += h.top(); h.pop();
sum += h.top(); h.pop();
h.push(sum);
res += sum;
}
cout << res;
return 0;
}
并查集
处理不相交可合并的集合关系的数据结构叫做并查集。使用前得初始化。
查询
int find(int n)
{
return n==f[n]?n:(f[n]=find(f[n]));
}
合并
void merge(int i,int j)
{
f[find(i)]=find(j);
}
典例题
P1551 亲戚 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include <bits/stdc++.h>
using namespace std;
const int MAX = 2e6+5;
int f[MAX];
void init(int *f,int n)//初始化并查集
{
for (int i = 1; i <= n; i++)
f[i] = i;
}
int Find(int n)//查询父节点
{
return n == f[n] ? n : (f[n] = Find(f[n]));
}
void merge(int i,int j)//合并
{
f[Find(i)] = Find(j);
}
int main()
{
int p1, p2;
int n, m, p; cin >> n >> m >> p;
init(f, n);
for (int i = 1; i <= m; i++)
{
cin >> p1 >> p2;
merge(p1, p2);
}
for (int j = 1; j <= p; j++)
{
cin >> p1 >> p2;
if (Find(p1) == Find(p2))
cout << "Yes" << endl;
else
cout << "No" << endl;
}
return 0;
}
哈希表/散列表
一种字符串hash:
typedef unsigned long long LL;
int prime = 233317;
LL mod = 212370440130137957ll,base=123;
LL Hash(char s[])
{
int len = strlen(s);
LL res = 0;
for (int i = 0; i < len; i++)
res = (res * base + (LL)s[i]) % mod + prime;
return res;
}
STL hash
C++ STL 自带hash。
#include <iostream>
using namespace std;
int main()
{
string str = "Cirno is the strongest";
hash<string> hash_fn;
size_t str_hash = hash_fn(str);
cout << str_hash;
return 0;
}
解决冲突的方法
分离链接法
图
邻接矩阵
邻接表
#include <iostream>
#include <vector>
using namespace std;
const int MAX = 1000;
int m, n;//图中m顶点,n条边。
vector<pair<int, int>> P[MAX];
int v[MAX][MAX];
int main()
{
cin >> m >> n;
for (int i = 1; i <= m; i++)
{
int u, v, l; cin >> u >> v >> l;
P[u].push_back({ v,l });
//P[v].push_back({u,l})
//如果是无向图,则还需要将终点,起始点颠倒过来存储一下
}
//将邻接表转换为邻接矩阵
for (int i = 1; i <= m; i++)
for (int j = 0; j < P[i].size(); j++)
v[i][P[i][j].first] = P[i][j].second;
//对于邻接矩阵v[i][j],j就是终点,即为P[i][j].first,
//其储存的值为边权,也就是P[i][j].second
//output
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= m; j++)
cout << v[i][j] << " ";
cout << endl;
}
return 0;
}
图的遍历
DFS遍历
void dfs(int x)
{
vis[x] = true;
cout << x << " ";
for (int i=0; i<P[x].size(); i++)
if (!vis[P[x][i]])
dfs(P[x][i]);
}
BFS遍历
queue<int> q;
void bfs(int x)
{
memset(vis, false, sizeof(vis));
vis[x] = true;
q.push(x);
while (!q.empty())
{
int v = q.front();
q.pop();
cout << v << " ";
for (int i=0; i<P[v].size(); i++)
if (!vis[P[v][i]])
{
vis[P[v][i]] = true;
q.push(P[v][i]);
}
}
}
DAG与拓扑排序
DAG:对于一个图而言,如果这个图是没有环的,但是边是有方向的,那么就称之为有向无环图,即DAG。
拓扑排序:拓扑排序就是在DAG的基础上对点进行排序,使得在搜到点x时所有能到达点x的点y已经被搜过了。
其具体实现流程如下:
1.将所有入度为0的点加入处理队列
2.将处于队头的点x取出,遍历x所能到达的所有点y。
- 对于每一个y,删除从点x到点y的边。
4.如果点y的入度减为0了,说明说明所有能到y的点都被计算过了,再将点y加入处理队列。
5.重复2,直到队列为空。
一道用到拓扑排序的经典例题:
P4017 最大食物链计数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include<bits/stdc++.h>
using namespace std;
const int MAX = 5e5 + 10;
const int MOD = 80112002;
vector<int> V[MAX];
queue<int> Q;
int ind[MAX], outd[MAX], f[MAX];//入度,出度,食物链数
int main()
{
int n, m, res = 0; cin >> n >> m;
for (int i = 0; i < m; i++)
{
int x, y; cin >> x >> y;
outd[x]++; ind[y]++;
V[x].push_back(y);
}
for (int i = 1; i <= n; i++) {
if (ind[i] == 0)
Q.push(i),f[i]=1;
}//将入度为0的数据入队
while (!Q.empty()) {
int x = Q.front(); Q.pop();
for (int i = 0; i < V[x].size(); i++) {
int y = V[x][i];
f[y] = (f[x] + f[y]) % MOD;
ind[y]--;
if (ind[y] == 0)
Q.push(y);//入度为0,就入队
}
}
for (int i = 1; i <= n; i++)
{
if (outd[i] == 0)
res = (res + f[i]) % MOD;
}
cout << res;
return 0;
}
最短路问题
无权最短路问题
运用到了BFS ,没有访问到的标记为INF。
#include <bits/stdc++.h>
using namespace std;
const int MAX = 1e6;
const int MOD = 100003;
struct Node {
bool vis=false;
int Dist = INFINITY;
int path;
};
vector<int> V[MAX];
queue<int> Q;
Node NodeArr[MAX];
int main() {
int m, n; cin >> m >> n;
for (int i = 1; i <= n; i++) {
int x, y; cin >> x >> y;
V[x].push_back(y);
V[y].push_back(x);
}
for (int i = 1; i <= m; i++) {
NodeArr[i].vis = false;
NodeArr[i].Dist = INFINITY;
}
Q.push(1); NodeArr[1].Dist = 0; NodeArr[1].vis = true;
while (!Q.empty()) {
int x = Q.front(); Q.pop();
for (int i = 0; i < V[x].size(); i++) {
if (NodeArr[V[x][i]].vis==false) {
NodeArr[V[x][i]].vis = true;
NodeArr[V[x][i]].Dist = NodeArr[x].Dist + 1;
NodeArr[V[x][i]].path = x;
Q.push(V[x][i]);
}
}
}
for (int i = 1; i <= m; i++)
cout << NodeArr[i].Dist << endl;
return 0;
}
P1144 最短路计数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
以下为这题题解,这题除了可以求出1到各个结点的无权最短路长度以外,还可以求出到不同结点一共有多少种最短路情况。
#include <bits/stdc++.h>
using namespace std;
const int MAX = 1e6;
const int MOD = 100003;
vector<int> V[MAX];
queue<int> Q;
bool judge[MAX];
int cnt[MAX],depth[MAX];
int main() {
int m, n; cin >> m >> n;
for (int i = 1; i <= n; i++) {
int x, y; cin >> x >> y;
V[x].push_back(y);
V[y].push_back(x);
}
for (int i = 1; i <= m; i++) sort(V[i].begin(), V[i].end());
Q.push(1); judge[1] = 1; cnt[1] = 1;
while (!Q.empty()) {
int x = Q.front(); Q.pop();
for (int i = 0; i < V[x].size(); i++) {
int n = V[x][i];
if (!judge[n]) {
judge[n] = 1;
depth[n] = depth[x] + 1;
Q.push(n);
}
if(depth[n]==depth[x]+1) cnt[n]=(cnt[n]+cnt[x])%MOD;
}
}
for (int i = 1; i <= m; i++)
cout << cnt[i] << endl;
return 0;
}
Dijkstra算法
基于贪心思想,使用堆优化,可以求得有权最短路径的结果
#include <bits/stdc++.h>
using namespace std;
const int MAX = 2e5+5;
const int INF = 2147483647;
struct Node {
int dist = INF;
bool vis = false;
};
typedef pair<int, int> pll;
Node NodeArr[MAX];
vector<pll> V[MAX];
priority_queue<pll,vector<pll>,greater<pll>> Q;
void Dijkstra(vector<pair<int, int>> V[], int start) {
NodeArr[start].dist = 0;
Q.push({ 0,start });
while (!Q.empty()) {
int x = Q.top().second; Q.pop();
if (NodeArr[x].vis)
continue;
NodeArr[x].vis = true;
for (int i = 0; i < V[x].size(); i++)
{
int temp = NodeArr[x].dist + V[x][i].first;
if (NodeArr[V[x][i].second].dist > temp)
NodeArr[V[x][i].second].dist = temp,Q.push({ NodeArr[V[x][i].second].dist, V[x][i].second });
}
}.
}
int main() {
int n, m, s; cin >> n >> m >> s;
for (int i = 1; i <= m; i++) {
int x, y, l; scanf("%d%d%d", &x, &y, &l);
V[x].push_back({ l, y });
}
Dijkstra(V, s);
for (int i = 1; i <= n; i++)
printf("%d ", NodeArr[i].dist);
return 0;
}
floyd算法
可以求出任意两节点之间的最短路径,因此它是比Dijkstra更一般的算法。
#include <bits/stdc++.h>
using namespace std;
const int MAX = 5e3 + 5;
const int INF = 2e5;
int Martix[MAX][MAX];
int n, m;
int pathMatirx[MAX][MAX];//存储中间点
int shortPath[MAX][MAX];//存储两边之间最小值
void floyd(int x,int y) {//起点,终点
//init
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) {
pathMatirx[i][j] = j;
shortPath[i][j] = Martix[i][j];
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
for (int k = 1; k <= n; k++) {
if (shortPath[j][k] > (shortPath[j][i] + shortPath[i][k])) {
shortPath[j][k] = shortPath[j][i] + shortPath[i][k];
pathMatirx[j][k] = pathMatirx[j][i];
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++)
cout << shortPath[i][j] << " ";
cout << endl;
}
cout << x << " ";
int k = pathMatirx[x][y];
while (k != y) {
cout << k << " ";
k = pathMatirx[k][y];
}
cout << y << endl;
cout << shortPath[x][y] << endl;
}
int main() {
std::ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) {
if (i == j)
Martix[i][j] = 0;
else
Martix[i][j] = INF;
}
for (int i = 1; i <= m; i++) {
int x, y, v; cin >> x >> y >> v;
Martix[x][y] = v;
Martix[y][x] = v;
}
floyd(1,2);
return 0;
}
最小生成树
Prim算法
算法和dijkstra非常像,甚至可以说是一模一样.
#include <bits/stdc++.h>
using namespace std;
const int MAX = 2e5 + 5;
const int INF = 1<<30;
typedef pair<long , long > pll;//权重,终点
struct Node {
long long dist = INF;
bool vis = false;
} node[MAX];
priority_queue<pll, vector<pll>, greater<pll>> q;
vector<pll> V[MAX];
int N, M;
void prim() {
int ans = 0, tot = 0;
q.push({ 0,1 });
node[1].dist = 0;
while (!q.empty()) {
pll x = q.top();
q.pop();
if (node[x.second].vis)
continue;
tot++;
ans += node[x.second].dist;
node[x.second].vis = true;
for (auto i : V[x.second]) {
if (!node[i.second].vis && i.first < node[i.second].dist) {
node[i.second].dist = i.first;
q.push({ node[i.second].dist,i.second });
}
}
}
if (tot != N)
cout << "orz" << endl;
else
cout << ans << endl;
}
int main(void) {
std::ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> N >> M;
for (int i = 1; i <= M; i++) {
int x, y, l;
cin >> x >> y >> l;
V[x].push_back({ l,y });
V[y].push_back({ l,x });
}
prim();
return 0;
}
Kruskal算法
也是一种求最小生成树的算法,使用到了并查集的知识,将结点根据权值有小到大进行排列,从第一个结点开始遍历,如果结点起点和终点不属于同一个集合,则将两点合并到同一个集合,并且使计数cnt--,ans增加起点到终点的权值,最后可以到达求最小生成树的目的。其基本思想是贪心。
#include <bits/stdc++.h>
using namespace std;
const int MAX=2e5+5;
int m,n,f[MAX];
struct Node{
int x,y,v;
bool operator<(const Node& a) const{
return v<a.v;
}
} M[MAX];
int find(int n){
return n==f[n]?n:(f[n]=find(f[n]));
}
void merge(int x,int y){
f[find(x)]=find(y);
}
void init(int n){
for(int i=1;i<=n;i++)
f[i]=i;
}
void Kurskal(){
init(m);
sort(M+1,M+1+n);
int ans=0,cnt=m;
for(int i=1;i<=n;i++){
int x=find(M[i].x),y=find(M[i].y);
if(x!=y){
merge(x,y);
ans+=M[i].v;
cnt--;
}
if(cnt==1)
break;
}
if(cnt!=1)
cout<<"orz"<<endl;
else
cout<<ans<<endl;
}
int main(){
cin>>m>>n;
for(int i=1;i<=n;i++){
cin>>M[i].x>>M[i].y>>M[i].v;
}
Kurskal();
return 0;
}
java快读类
鉴定为十分有用
/** 快速输入类 */
static class Reader {
static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
static StringTokenizer tokenizer = new StringTokenizer("");
/** 获取下一段文本 */
static String next() throws IOException {
while ( ! tokenizer.hasMoreTokens() ) {
tokenizer = new StringTokenizer(reader.readLine());
}
return tokenizer.nextToken();
}
static int nextInt() throws IOException {
return Integer.parseInt( next() );
}
static double nextDouble() throws IOException {
return Double.parseDouble( next() );
}
}
标签:return,int,MAX,汇总,else,算法,Tree,id
From: https://www.cnblogs.com/cirth9/p/18508625