算法学习笔记
chapter1 计划
chapter2 排序
1快速排序算法学习
//快速排序
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
int n;
int a[N];
/*
void quick_sort(int a[],int l,int r)
{
if(l>=r)
return;
int x=a[l],i=l-1,j=r+1;
while(i<j)
{
do i++;while(a[i]<x);
do j--;while(a[j]>x);
if(i<j)
swap(a[i],a[j]);
}
quick_sort(a,l,j);
quick_sort(a,j+1,r);
}*/
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
}
sort(a,a+n);
for(int i=0;i<n;i++)
{
printf("%d ",a[i]);
}
return 0;
}
2 归并排序算法学习
//归并排序
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
int n;
int a[N],tmp[N];
void merge_sort(int a[],int l,int r)
{
if(l>=r)
return;
int mid=(l+r)>>1;
merge_sort(a,l,mid),merge_sort(a,mid+1,r);
int i=l,j=mid+1,k=0;
while(i<=mid&&j<=r)
{
if(a[i]<=a[j])
{
tmp[k++]=a[i++];
}
else
{
tmp[k++]=a[j++];
}
}
while(i<=mid)
tmp[k++]=a[i++];
while(j<=r)
tmp[k++]=a[j++];
for(i=l,j=0;i<=r;i++,j++)
a[i]=tmp[j];
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
}
//sort(a,a+n);
merge_sort(a,0,n-1);
for(int i=0;i<n;i++)
{
printf("%d ",tmp[i]);
}
return 0;
}
chapter3 图论
1.prim算法
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
#define inf 1e9
struct edge
{
int y;//指向的顶点
int z;//距离
};
const int N=5010;
int x,y,z,n,m,sum,cnt;//sum为最终结果,cnt记录出队的顶点
int d[N],vis[N];//d为距离,vis记录是否被出队
vector<edge> e[N];//记录图信息
priority_queue<pair<int,int>> q;//建立优先队列(最大的排在最上面(大顶堆),若在距离前加负号可将距离最小的放到堆顶)
bool prim(int s)
{
//初始化距离d
for(int i=0;i<=n;i++)
{
d[i]=inf;
}
d[s]=0;//从1号结点开始
q.push({0,s});//距离为0,顶点为1
while(q.size())
{
int u=q.top().second;//取出队顶 顶点
q.pop();
if(vis[u])
continue;
vis[u]=1;
sum+=d[u];
cnt++;
for(auto t:e[u])
{
int y=t.y;//点
int z=t.z;//距离
if(d[y]>z)
{
d[y]=z;
q.push({-d[y],y});
}
}
}
return cnt==n;
}
//建立大根堆
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<m;i++)
{
scanf("%d%d%d",&x,&y,&z);
e[x].push_back({y,z});//无向图
e[y].push_back({x,z});
}
if(!prim(1))
{
printf("orz\n");
}
else
{
printf("%d",sum);
}
return 0;
}
2.Kruscal算法
//利用并查集来建立最优二叉树
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 2e5 + 10;
struct edge
{
int u, v, w;
// 重载小于号
bool operator<(const edge &t) const
{
return w < t.w;
}
} e[N];
int u, v, w, m, n;
int fa[N], sum, ans = 0; // sum为最小生成树边权之和,ans为构成最小生成树的边数
int find(int x)
{
if (fa[x] == x)
return x;
else
return fa[x] = find(fa[x]);
}
bool kruskal()
{
for (int i = 1; i <= n; i++)
fa[i] = i;
sort(e, e + m);
for (int i = 0; i < m; i++)
{
int x = find(e[i].u);
int y = find(e[i].v);
if (x != y)
{
// 可以进行合并
fa[x] = y;
ans++;
sum += e[i].w;
}
}
return ans == n - 1;
}
int main()
{
cin >> n >> m;
for (int i = 0; i < m; i++)
{
cin >> u >> v >> w;
e[i] = {u, v, w};
}
if (kruskal())
{
cout << sum << endl;
}
else
{
cout << "orz" << endl;
}
return 0;
}
3 kahn算法(拓扑排序)
/*
注意:以下代码只适用于给结点的数字从1开始,且连续的情况下,用于排序
*/
#include<iostream>
#include<queue>
#include<vector>
using namespace std;
const int N=1e5+10;
int n,m,a,b;//n为结点数,m为边数
vector<int> e[N],tp;
int din[N];//保存结点的入度
bool toposort()
{
queue<int> q;//在队列中保存入度为0的结点
for(int i=1;i<=n;i++)
{
if(din[i]==0)
{
q.push(i);
}
}
while(q.size())
{
int x=q.front();
q.pop();
tp.push_back(x);
for(auto y:e[x])
{
if(--din[y]==0)
{
q.push(y);
}
}
}
return tp.size()==n;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<m;i++)
{
scanf("%d%d",&a,&b);
e[a].push_back(b);
din[b]++;
}
if(!toposort())
{
printf("没有拓扑序\n");
}
else
{
for(auto t:tp)
{
printf("%d ",t);
}
}
return 0;
}
4 DFS算法
#include<iostream>
#include<algorithm>
#include<vector>
#include <string.h>
using namespace std;
const int N = 1e5+10;
int m,n,a,b;
vector<int> e[N],tp;
int c[N];//染色数组
bool dfs(int x)
{
c[x]=-1;
for(int y:e[x])
{
if(c[y]<0)
return 0;
else if(!c[y])
{
if(!dfs(y))
return 0;
}
}
c[x]=1;
tp.push_back(x);
return 1;
}
bool toposort()
{
memset(c,0,sizeof(c));
for(int i=1;i<=n;i++)
{
if(!c[i])
{
if(!dfs(i))
return 0;
}
}
reverse(tp.begin(),tp.end());
return 1;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<m;i++)
{
scanf("%d%d",&a,&b);
e[a].push_back(b);
}
if(!toposort())
{
cout<<"不能进行拓扑排序"<<endl;
}
else
{
for(auto y:tp)
{
cout<<y<<" "<<endl;
}
}
return 0;
}
5 最短路Dijkstra算法
/*
P4779 【模板】单源最短路径(标准版)
https://www.luogu.com.cn/problem/P4779
*/
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
#define INF 1e9;
struct edge
{
int v, w;
};
const int N = 2e5 + 10;
int n, m, s, u, v, w;
vector<edge> e[N]; // 存储输入信息
int d[N],vis[N];//d保存距离,vis判断是否被访问
void dijkstra(int s)
{
priority_queue<pair<int,int>> q;
//初始化距离
for(int i=1;i<=n;i++)
d[i]=INF;
d[s]=0;//起点
q.push({0,s});
while(q.size())
{
int u=q.top().second;
q.pop();
if(vis[u])
continue;
vis[u]=1;
for(auto t:e[u])
{
int v=t.v,w=t.w;
if(d[v]>d[u]+w)
{
d[v]=d[u]+w;
q.push({-d[v],v});
}
}
}
}
int main()
{
cin >> n >> m >> s;
for (int i = 0; i < m; i++)
{
cin >> u >> v >> w;
e[u].push_back({v,w});
}
dijkstra(s);
for(int i=1;i<=n;i++)
{
cout<<d[i]<<" ";
}
return 0;
}
6 SPFA判负环以及解决有负边权的单元最短路径问题
----有问题
//DFS_spfa 判负环 会卡点 #9
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=2010,M=6010;
int n,m;
int to[M],ne[M],w[M],h[N],tot;
int d[N],vis[N];
void add(int a,int b,int c){
to[++tot]=b;w[tot]=c;
ne[tot]=h[a];h[a]=tot;
}
bool spfa(int u){ //判负环
vis[u]=1;
for(int i=h[u];i;i=ne[i]){
int v=to[i];
if(d[v]>d[u]+w[i]){
d[v]=d[u]+w[i];
if(vis[v]||spfa(v))return 1;
}
}
vis[u]=0;
return 0;
}
int main(){
int T; scanf("%d",&T);
while(T--){
tot=0; memset(h,0,sizeof(h));
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
if(w>=0)add(v,u,w);;
}
memset(d,0x3f,sizeof d);d[1]=0;
memset(vis,0,sizeof vis);
puts(spfa(1)?"YES":"NO");
}
return 0;
}
7最短路 Floyd 算法(全源最短路径)
void floyed
{
for(int k=1;k<=n;k++)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
}
}
}
8 Jhonson最短路(思想重要,比赛不易遇到--难 理解欠缺)
// Luogu P5905 【模板】Johnson 全源最短路
#include <iostream>
#include <vector>
#include <queue>
#include<cstring>
using namespace std;
struct edge
{
int v, w;
};
const int inf=1000000000;
const int N = 6010;
vector<edge> e[N];
int n, m, u, v, w, vis[N],cnt[N];
long long h[N],d[N];
//闭环
void spfa()
{
memset(h,63,sizeof h);
memset(vis,0,sizeof vis);
queue<int>q;
h[0]=0,vis[0]=1,q.push(0);
while(q.size())
{
int u=q.front();
q.pop();
vis[u]=0;
for(auto t:e[u])
{
int v=t.v,w=t.w;
if(h[v]>h[u]+w)
{
h[v]=h[u]+w;
cnt[v]=cnt[u]+1;
if(cnt[v]>n)
{
cout<<-1<<endl;
exit(0);
}
if(!vis[v])
{
q.push(v);
vis[v]=1;
}
}
}
}
}
void dijkstra(int s)
{
priority_queue<pair<long,long>> q;
for(int i=1;i<=n;i++)
d[i]=inf;
memset(vis,0,sizeof vis);
d[s]=0;
q.push({0,s});
while(q.size())
{
int u=q.top().second;
q.pop();
if(vis[u])
continue;
vis[u]=1;
for(auto t:e[u])
{
int v=t.v,w=t.w;
if(d[v]>d[u]+w)
{
d[v]=d[u]+w;
if(!vis[v])
q.push({-d[v],v});
}
}
}
}
int main()
{
cin>>n>>m;
for(int i=0;i<m;i++)
{
cin>>u>>v>>w;
e[u].push_back({v,w});
}
//加虚拟边
for(int i=1;i<=n;i++)
{
e[0].push_back({i,0});
}
spfa();
//构造新边
for(int i=1;i<=n;i++)
{
for(auto &t:e[i])
{
t.w+=h[i]-h[t.v];
}
}
for(int i=1;i<=n;i++)
{
dijkstra(i);
long long ans=0;
for(int j=1;j<=n;j++)
{
if(d[j]==inf)
{
ans+=(long long)j*inf;
}
else
{
ans+=(long long)j*(d[j]+h[j]-h[i]);
}
}
printf("%lld\n",ans);
}
return 0;
}
9 无向图的最小环问题
#include <iostream>
#include<cstring>
#include <algorithm>
using namespace std;
const int N = 5010;
int n, m, u, v, w, d[N][N], W[N][N], ans = 1e8;
void floyd()
{
for (int k = 1; k <= n; k++)
{
for (int i = 1; i < k; i++)
{
for (int j = i + 1; j < k; j++)
{
ans = min(ans, d[i][j] + W[k][j] + W[i][k]);
}
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
}
}
if (ans == 1e8)
{
cout << "No solution." << endl;
}
else
cout << ans << endl;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (i != j)
W[i][j] = 1e8;
for (int i = 0; i < m; i++)
{
cin >> u >> v >> w;
W[u][v] = W[v][u] = w;
}
memcpy(d, W, sizeof d);
floyd();
}
10 最近公共祖先
1)倍增算法
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10, M = 2 * N;
struct edge
{
int v, ne;
} e[M]; // 存边集
int m, n, s, a, b;
int fa[N][22], dep[N]; // 这里的fa[u][i]表示u向上跳2^i之后的点,i是从0-20,当i=0时即为u的父节点,dep保存每个结点的深度
int h[N], idx; // h存点的第一条出边.idx全局变量
void add(int a, int b)
{
e[++idx] = {b, h[a]};
h[a] = idx;
}
void dfs(int u, int father)
{
dep[u] = dep[father] + 1;
fa[u][0] = father;
for (int i = 0; i <= 20; i++)
{
fa[u][i + 1] = fa[fa[u][i]][i]; // 相当于跳两步——>2^i 除以2,先向上跳一半,再从当前的点向上跳另一半
}
for (int i = h[u]; i; i = e[i].ne)
if (e[i].v != father)
dfs(e[i].v, u);
}
int lca(int x, int y)
{
if (dep[x] < dep[y])
swap(x, y);
// 先跳到同一层
for (int i = 20; i >= 0; i--)
{
if (dep[fa[x][i]] >= dep[y])
x = fa[x][i];
}
if (x == y)
return y;
// 再跳到LCA的下一层
for (int i = 20; i >= 0; i--)
{
if (fa[x][i] != fa[y][i]) // 若相同,则为x,y的公共祖先
x = fa[x][i], y = fa[y][i];
}
return fa[x][0];
}
int main()
{
scanf("%d%d%d", &n, &m, &s);
// memset(h,-1,sizeof (h));
for (int i = 1; i < n; i++)
{
scanf("%d%d", &a, &b);
add(a, b);
add(b, a);
}
dfs(s, 0);
while (m--)
{
scanf("%d%d", &a, &b);
printf("%d\n", lca(a, b));
}
return 0;
}
2)Tarjan(塔扬)算法——最近公共祖先2
不太熟练,之后需要巩固
/**
* 该算法基于并查集
* fa存储父节点
* e[]存储结点信息
* vis[]打标记,判断是否被访问
* query记录查询
* ans[]存储查询结果
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
const int M = 2 * N;
int fa[N], vis[N], ans[M]; // 存储父节点
vector<int> e[N];
vector<pair<int, int>> query[N];
int n, m, s, x, y, a, b;
int find(int x)
{
if (fa[x] == x)
return x;
else
return fa[x] = find(fa[x]);
}
void tarjan(int u)
{
vis[u] = true;
for (auto v : e[u])
{
if (!vis[v])
{
tarjan(v);
fa[v] = u;
}
}
for (auto q : query[u])
{
int y = q.first, i = q.second;
if (vis[y])
ans[i] = find(y);
}
}
int main()
{
cin >> n >> m >> s;
for (int i = 0; i < n - 1; i++)
{
cin >> x >> y;
e[x].push_back(y);
e[y].push_back(x);
}
for (int j = 1; j <= m; j++)
{
cin >> a >> b;
query[a].push_back({b, j});
query[b].push_back({a, j});
}
for (int i = 1; i < N; i++)
{
fa[i] = i;
}
tarjan(s);
for (int i = 1; i <= m; i++)
{
cout << ans[i] << endl;
}
return 0;
}
chapter4 数据结构
1 并查集
#include <iostream>
using namespace std;
const int N = 2e5 + 10;
int n, m, x, y, z, f[N]; // 这里的f数组保存结点的根节点,以便于压缩路径
int find(int x)
{
if (f[x] == x)
return x;
else
return f[x] = find(f[x]);//进行路径压缩
}
void unionset(int x, int y)
{
f[find(x)] = find(y);//合并
}
int main()
{
cin >> n >> m;
// 初始化f数组
for (int i = 1; i <= n; i++)
f[i] = i;
for (int i = 0; i < m; i++)
{
cin >> z >> x >> y;
if (z == 1)
{
// 进行合并
unionset(x, y);
}
else
{
int a = find(x), b = find(y);
// 判断是否在同意集合中
if (a == b)
cout << "Y" << endl;
else
cout << "N" << endl;
}
}
return 0;
}
2 线段树+懒标记
本题需要之后再次复习,掌握不够完善
P3374 【模板】树状数组 1
//解决区间更新,统计某个区间和等问题 在大量更新、查找混合时,快速方便
#include <iostream>
using namespace std;
#define le p << 1
#define re p << 1 | 1
const int N = 5e5 + 10;
struct tree
{
int l, r, sum;
} tr[N * 4];
int w[N]; // 初始化值
int n, m, z, x, y;
// 建立线段树
void build(int p, int l, int r)
{
tr[p] = {l, r, w[l]};
if (l == r)
return; // 叶子结点
int mid = (l + r) >> 1;
build(le, l, mid);
build(re, mid + 1, r);
tr[p].sum = tr[le].sum + tr[re].sum;
}
// 点修改
/**
* 这里的p为1,从根节点开始查找
* x为更改的位置
* k为要更改的值
*/
void update(int p, int x, int k)
{
if (tr[p].l == x && tr[p].r == x)
{
tr[p].sum += k;
return;
}
int mid = tr[p].l + tr[p].r >> 1;
if (x <= mid)
update(le, x, k);
else
update(re, x, k);
tr[p].sum = tr[le].sum + tr[re].sum;
}
/**
* 区间查找
*
*/
int query(int p, int x, int y)
{
if (tr[p].l >= x && tr[p].r <= y)
return tr[p].sum;
int mid = tr[p].l + tr[p].r >> 1;
int sum = 0;
if (x <= mid)
sum += query(le, x, y);
if (y > mid)
sum += query(re, x, y);
return sum;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> w[i];
}
build(1, 1, n);
for (int i = 0; i < m; i++)
{
cin >> z >> x >> y;
if (z == 1)
{
update(1, x, y);
}
else
{
cout << query(1, x, y) << endl;
}
}
return 0;
}
3 树状数组
//向后修,向前查
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
int n, m, k, x, y;
int a[N], s[N];
/**
* 向前查,向后修
*/
// 提取x的低位二次幂数
/**
* 3->011 & 101 得到 001 则3的低位2次幂数为1
*
* 在树状数组中 x+x的低位二次幂数等于父结点的下标
* 如 4 其二次幂数为100 ->4+4=8,则4的父节点下标为8
*
* 同一层,低位的二次幂数相同
*/
int lowbit(int x)
{
return x & -x;
}
// 向后修
void change(int x, int k)
{
while (x <= n)
{
s[x] += k;
// 向后面的父节点依次加
x += lowbit(x);
}
}
// 向前查
int query(int x)
{
int t = 0;
while (x)
{
t += s[x];
x -= lowbit(x);
}
return t;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
change(i, a[i]); // 向后构造树状数组
}
for (int i = 1; i <= m; i++)
{
cin >> k >> x >> y;
if (k == 1)
{
change(x, y);
}
else
{
cout << query(y) - query(x - 1) << endl;
}
}
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
int a[N], s[N];
int n, m, op, x, y, k;
int lowbit(int x)
{
return x & -x;
}
// 向后修
void change(int x, int k)
{
while (x <= n)
{
s[x] += k;
x += lowbit(x);
}
}
// 向前查
int query(int x)
{
int t = 0;
while (x)
{
t += s[x];
x -= lowbit(x);
}
return t;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
for (int i = 1; i <= m; i++)
{
cin >> op >> x;
if (op == 1)
{
cin >> y >> k;
// 差分
change(x, k), change(y + 1, -k);
}
else
printf("%d\n", a[x] + query(x));
}
return 0;
}
4 单调队列
/**
* 单调队列
* 用两个指针维护单调队列,h为队头指针,t为队尾指针
* 队列中的元素范围为:i-k+1<j<i 这里的j用数组q保存
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
LL a[N], q[N];
int n, k;
int main()
{
cin >> n >> k;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
// 求最小值
int h = 1, t = 0; // 清空队列
for (int i = 1; i <= n; i++) // 枚举序列
{
while (h <= t && q[h] < i - k + 1) // 队头出队(队列不空,且对头元素滑出窗口)
h++;
while (h <= t && a[q[t]] >= a[i]) // 队尾出队(队列不空,且新元素更优)
t--;
q[++t] = i;
if (i >= k)
printf("%d ", a[q[h]]);
}
puts("");
// 求最大值
h = 1, t = 0;
for (int i = 1; i <= n; i++)
{
while (h <= t && q[h] < i - k + 1)
h++;
while (h <= t && a[q[t]] <= a[i])
t--;
q[++t] = i;
if (i >= k)
printf("%d ", a[q[h]]);
}
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 10;
int a[N], q[N];
int n, m;
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
if (n >= 1)
puts("0");
// 求最小值
int h = 1, t = 0;
for (int i = 1; i < n; i++)
{
while (h <= t && q[h] < i - m + 1)
h++;
while (h <= t && a[q[t]] >= a[i])
t--;
q[++t] = i;
// if (i >= m)
// cout << a[q[h]] << endl;
printf("%d\n", a[q[h]]);
}
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 10;
int a[N], q[N];
int n, k;
int main()
{
cin >> n >> k;
for (int i = 1; i <= n; i++)
cin >> a[i];
// 求区间最大值
int h = 1, t = 0;
for (int i = 1; i <= n; i++)
{
while (h <= t && q[h] < i - k + 1)
h++;
while (h <= t && a[q[t]] <= a[i])
t--;
q[++t] = i;
if (i >= k)
cout << a[q[h]] << endl;
}
return 0;
}
[Luogu P2216 HAOI2007]理想的正方形
#include <bits/stdc++.h>
using namespace std;
const int N = 1010, INF = 1e9;
int w[N][N]; // 保存矩阵
int minv[N][N], maxv[N][N]; // 按行计算最大值和最小值
int q[N], a[N], b[N], c[N], d[N];
int n, m, k;
// 区间最大值
void get_max(int a[], int b[], int m)
{
int h = 1, t = 0;
for (int i = 1; i <= m; i++)
{
while (h <= t && q[h] < i - k + 1)
h++;
while (h <= t && a[q[t]] <= a[i])
t--;
q[++t] = i;
b[i] = a[q[h]]; // 记录最大值
}
}
// 区间最小值
void get_min(int a[], int b[], int m)
{
int h = 1, t = 0;
for (int i = 1; i <= m; i++)
{
while (h <= t && q[h] < i - k + 1)
h++;
while (h <= t && a[q[t]] >= a[i])
t--;
q[++t] = i;
b[i] = a[q[h]]; // 记录最小值
}
}
int main()
{
cin >> n >> m >> k;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
scanf("%d", &w[i][j]);
// 按行计算
for (int i = 1; i <= n; i++)
{
get_max(w[i], maxv[i], m);
get_min(w[i], minv[i], m);
}
int res = INF;
// 枚举列
for (int j = k; j <= m; j++)
{
for (int i = 1; i <= n; i++)
{
a[i] = maxv[i][j];
b[i] = minv[i][j];
}
get_max(a, c, n);
get_min(b, d, n);
for (int i = k; i <= n; i++)
res = min(res, c[i] - d[i]);
}
cout << res << endl;
return 0;
}
5 哈夫曼树
#include <iostream>
#include <queue>
#include <algorithm>
using namespace std;
typedef long long ll;
/**
* 其原理是构建k叉哈夫曼树
*/
struct node
{
ll w, h;
node() { w = 0, h = 0; }
/**
* 这种方法比在构造函数体内逐一对成员变量赋值要更高效,
* 因为它直接在对象创建时就将成员变量初始化为指定的值。
*/
node(ll w, ll h) : w(w), h(h) {}
//这里其实是由小到大进行排序,使得在优先队列中,最小的(出现次数最少)在堆顶
bool operator<(const node &a) const
{
return w == a.w ? h > a.h : w > a.w;
}
};
priority_queue<node> q;
ll ans, n, k, w;
int main()
{
cin >> n >> k;
for (int i = 1; i <= n; i++)
{
cin >> w;
q.push(node(w, 1));
}
while ((q.size() - 1) % (k - 1) != 0)
q.push(node(0, 1));
// 构造哈夫曼树
while (q.size() >= k)
{
ll h = -1, w = 0;
for (int i = 0; i < k; i++)
{
node t = q.top();
q.pop();
h = max(h, t.h);
w += t.w;
}
ans += w;
q.push(node(w, h + 1));
}
printf("%lld\n%lld\n", ans, q.top().h - 1);
return 0;
}
chapter5 动态规划
1 线性DP
1) 最长上升子序列 二分优化
/**
* 主要原理:
* 保证子序列最优
* 若大于当前值,则追加到末尾
* 否则,替换当前子序列中第一个大于它的元素
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N], n, len; // a为输入的序列,b保存当前最优子序列
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9')
{
if (ch == '-')
f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
// 注意是最小化查找
int find(int x)
{
int l = 0, r = len + 1;
while (l + 1 < r)
{
int mid = (l + r) >> 1;
if (b[mid] >= x)
r = mid;
else
l = mid;
}
return r;
}
/**
* b数组中的元素并不是最大的上升子序列,仅仅数量与最长上升子序列的一致
*/
int main()
{
n = read();
for (int i = 1; i <= n; i++)
a[i] = read();
b[1] = a[1];
len = 1;
for (int i = 2; i <= n; i++)
{
if (b[len] < a[i])
{
b[++len] = a[i];
}
else
{
//b[find(a[i])] = a[i];
/**
* lower_bound 返回指向第一个值不小于val的位置,也就是返回第一个大于等于val值的位置。(通过二分查找)
* 与其相反的是upper_bound
*/
*lower_bound(b,b+len,a[i])=a[i];
}
}
cout << len << endl;
return 0;
}
2)最长公共子序列
//n^2的做法
//对于本题要考虑到全排列的性质
#include<iostream>
#include<cstdio>
using namespace std;
int a[100001],b[100001],map[100001],f[100001];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++){scanf("%d",&a[i]);map[a[i]]=i;}
for(int i=1;i<=n;i++){scanf("%d",&b[i]);f[i]=0x7fffffff;}
int len=0;
f[0]=0;
for(int i=1;i<=n;i++)
{
int l=0,r=len,mid;
if(map[b[i]]>f[len])f[++len]=map[b[i]];
else
{
while(l<r)
{
mid=(l+r)/2;
if(f[mid]>map[b[i]])r=mid;
else l=mid+1;
}
f[l]=min(map[b[i]],f[l]);
}
}
cout<<len;
return 0
}
3)最长公共子串
#include <bits/stdc++.h>
using namespace std;
const int N = 255;
char a[N], b[N];
int f[N][N];
int ans;
int main()
{
cin >> a + 1 >> b + 1;
int la = strlen(a + 1), lb = strlen(b + 1);
int max = -1, flag = 0;
for (int i = 1; i <= la; i++)
{
for (int j = 1; j <= lb; j++)
{
if (a[i] == b[j])
{
f[i][j] = f[i - 1][j - 1] + 1;
}
else
f[i][j] = 0;
if (max < f[i][j])
{
max = f[i][j];
flag = i;
}
}
}
cout << max << endl;
for (int i = max - 1; i >= 0; i--)
cout << a[flag - i];
puts("");
}
4)记忆化搜索:数字三角形
Luogu P1216 [USACO1.5][IOI1994]数字三角形 Number Triangles
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[N][N]; // 计算和
int a[N][N]; // 存储树塔
int n;
int dfs(int x, int y)
{
if (f[x][y] != -1) // 已经被搜索过
return f[x][y];
if (x == n) // 到达底部边界
return f[x][y] = a[x][y];
return f[x][y] = max(dfs(x + 1, y), dfs(x + 1, y + 1)) + a[x][y];
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= i; j++)
{
cin >> a[i][j];
}
}
memset(f, -1, sizeof f);
dfs(1, 1);
cout << f[1][1] << endl;
}
5)方格dp---数字三角形
Luogu P1216 [USACO1.5][IOI1994]数字三角形 Number Triangles
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[N][N]; // 计算和
int a[N][N]; // 存储树塔
int n;
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= i; j++)
{
cin >> a[i][j];
}
}
for (int i = n; i >= 1; i--)
for (int j = 1; j <= i; j++)
f[i][j] = max(f[i + 1][j], f[i + 1][j + 1]) + a[i][j];
cout << f[1][1] << endl;
return 0;
}
[Luogu P1004 NOIP2000 提高组] 方格取数
#include <bits/stdc++.h>
using namespace std;
int a[12][12], s[12][12][12][12];
int n, x, y, v;
/**
* 此题要考虑到思维dp
* 由于分成两次dp处理时,由于不确定过多,无法求得最优解,因此从起点开始,让两个点一同开始走
* f[i,j,x,y] 最后,x+y==i+j 终点相同
*/
int main()
{
cin >> n;
for (int i = 1; i; i++)
{
cin >> x >> y >> v;
if (x == 0 && y == 0 && v == 0)
break;
else
a[x][y] = v;
}
for (int i = n; i >= 1; i--)
for (int j = n; j >= 1; j--)
for (int x = n; x >= 1; x--)
for (int y = n; y >= 1; y--)
{
int &t = s[i][j][x][y];
// 上一步
// t = s[i][j][x][y] = max(max(s[i - 1][j][x - 1][y], s[i - 1][j][x][y - 1]), max(s[i][j - 1][x - 1][y], s[i][j - 1][x][y - 1]));
t = s[i][j][x][y] = max(max(s[i + 1][j][x + 1][y], s[i + 1][j][x][y + 1]), max(s[i][j + 1][x + 1][y], s[i][j + 1][x][y + 1]));
if (x == i && y == j)
{
t += a[i][j]; // 走到同一个点
}
else
t += a[i][j] + a[x][y];
}
cout << s[1][1][1][1] << endl;
}
#include <bits/stdc++.h>
using namespace std;
int a[12][12], s[24][12][12];
int n, x, y;
/**
* 此题要考虑到思维dp
* 由于分成两次dp处理时,由于不确定过多,无法求得最优解,因此从起点开始,让两个点一同开始走
* f[i,j,x,y] 最后,x+y==i+j 终点相同
* 利用条件进行降维优化
* i+j=x+y=k,因此可以进行优化,s[k][i][x] 表示走了k步,1到达了i行,2到达了j行,1,2表示两条路径
*/
int main()
{
cin >> n;
while (cin >> x >> y >> a[x][y], x)
;
for (int k = 2 * n; k >= 2; k--)
for (int i = n; i >= 1; i--)
for (int x = n; x >= 1; x--)
{
int j = k - i, y = k - x;
if (j >= 1 && j <= n && y >= 1 && y <= n)
{
int &t = s[k][i][x];
t = max(max(s[k + 1][i + 1][x + 1], s[k + 1][i + 1][x]), max(s[k + 1][i][x + 1], s[k + 1][i][x]));
if (i == x)
{
t += a[i][j]; // 走到同一个点
}
else
t += a[i][j] + a[x][y];
}
}
cout << s[2][1][1] << endl;
}
[Luogu P1006 NOIP2008 提高组] 传纸条
#include <bits/stdc++.h>
using namespace std;
const int N = 55;
int a[N][N], m, n;
int s[N * 2][N][N];
/**
* 只要起点和终点一致
* 可以看作均从起点开始出发,到达终点两次
*/
int main()
{
cin >> m >> n;
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
cin >> a[i][j];
for (int k = m + n; k >= 2; k--)
{
for (int i = m; i >= 1; i--)
{
for (int x = m; x >= 1; x--)
{
int j = k - i, y = k - x;
if (j >= 1 && j <= n && y >= 1 && y <= n)
{
int &t = s[k][i][x];
t = max(max(s[k + 1][i + 1][x + 1], s[k + 1][i + 1][x]), max(s[k + 1][i][x + 1], s[k + 1][i][x]));
if (i == x)
t += a[i][j];
else
t += a[i][j] + a[x][y];
}
}
}
}
cout << s[2][1][1] << endl;
}
6)编辑距离问题
#include <bits/stdc++.h>
using namespace std;
const int N = 2010;
char a[N], b[N];
int f[N][N];
int main()
{
cin >> a >> b;
int la = strlen(a), lb = strlen(b);
for (int i = 1; i <= la; i++)
f[i][0] = i;
for (int j = 1; j <= lb; j++)
f[0][j] = j;
for (int i = 1; i <= la; i++)
{
for (int j = 1; j <= lb; j++)
{
if (a[i - 1] == b[j - 1])
f[i][j] = f[i - 1][j - 1];
else
{
f[i][j] = min(min(f[i][j - 1], f[i - 1][j]), f[i - 1][j - 1]) + 1;
}
}
}
cout << f[la][lb] << endl;
}
/**
* 使用滚动数组进行优化
* 降低空间复杂度
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 2010;
char a[N], b[N];
int f[N];
int t1, t2;
int main()
{
cin >> a >> b;
int la = strlen(a), lb = strlen(b);
for (int j = 1; j <= lb; j++)
f[j] = j; // f[0][j]=j;
for (int i = 1; i <= la; i++)
{
t1 = f[0]++; // t1等价于f[i-1][0]
for (int j = 1; j <= lb; j++)
{
t2 = f[j];
if (a[i - 1] == b[j - 1])
// f[i][j] = f[i - 1][j - 1];
f[j] = t1;
else
{
// f[i][j] = min(min(f[i][j - 1], f[i - 1][j]), f[i - 1][j - 1]) + 1;
f[j] = min(t1, min(f[j - 1], f[j])) + 1;
}
t1 = t2; // t1等价于f[i-1][j-1]
}
}
cout << f[lb] << endl;
}
2 背包DP
1) 01背包问题
[Luogu P2871 USACO07DEC]Charm Bracelet S
//朴素写法,会超内存限制
#include <bits/stdc++.h>
using namespace std;
const int N = 3410;
const int M = 13000;
int w[N], d[N], f[N][M], n, m;
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> w[i] >> d[i];
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (j < w[i])
f[i][j] = f[i - 1][j];
else
f[i][j] = max(f[i - 1][j], f[i - 1][j - w[i]] + d[i]);
}
}
cout << f[n][m] << endl;
}
//内存优化
#include <bits/stdc++.h>
using namespace std;
const int N = 3410;
const int M = 13000;
int w[N], d[N], f[M], n, m;
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> w[i] >> d[i];
}
for (int i = 1; i <= n; i++)
{
for (int j = m; j >= w[i]; j--)
{
f[j] = max(f[j], f[j - w[i]] + d[i]);
}
}
cout << f[m] << endl;
return 0;
}
[Luogu P1048 NOIP2005 普及组] 采药
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int t, m, f[N], T[N], V[N];
int main()
{
cin >> t >> m;
for (int i = 1; i <= m; i++)
cin >> T[i] >> V[i];
for (int i = 1; i <= t; i++)
{
for (int j = t; j >= T[i]; j--)
{
f[j] = max(f[j], f[j - T[i]] + V[i]);
}
}
cout << f[t] << endl;
return 0;
}
[ Luogu P1049 NOIP2001 普及组] 装箱问题
#include <bits/stdc++.h>
using namespace std;
const int N = 2e4 + 10;
int v[32], f[N];
int main()
{
int m, n;
cin >> m >> n;
for (int i = 1; i <= n; i++)
{
cin >> v[i];
}
for (int i = 1; i <= n; i++)
{
for (int j = m; j >= v[i]; j--)
{
f[j] = max(f[j], f[j - v[i]] + v[i]);
}
}
cout << m - f[m] << endl;
return 0;
}
[ Luogu P1060 NOIP2006 普及组] 开心的金明
#include <bits/stdc++.h>
using namespace std;
const int N = 3e4 + 10;
int n, m, f[N], v[N], p[N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= m; i++)
cin >> v[i] >> p[i];
for (int i = 1; i <= m; i++)
{
for (int j = n; j >= v[i]; j--)
{
f[j] = max(f[j], f[j - v[i]] + v[i] * p[i]);
}
}
cout << f[n] << endl;
return 0;
}
[Luogu P2639 USACO09OCT]Bessie's Weight Problem G
#include <bits/stdc++.h>
using namespace std;
const int N = 45010;
const int M = 510;
int h, n, s[M], f[N];
int main()
{
cin >> h >> n;
for (int i = 1; i <= n; i++)
{
cin >> s[i];
for (int j = h; j >= s[i]; j--)
{
f[j] = max(f[j], f[j - s[i]] + s[i]);
}
}
cout << f[h] << endl;
return 0;
}
2)完全背包问题
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
const int M=1e7+10;
long long f[M], a[N], b[N];
int t, m;
int main()
{
cin >> t >> m;
for (int i = 1; i <= m; i++)
{
cin >> a[i] >> b[i];
}
for (int i = 1; i <= m; i++)
{
for (int j = a[i]; j <= t; j++)
{
f[j] = max(f[j], f[j - a[i]] + b[i]);
}
}
cout << f[t] << endl;
return 0;
}
// 滚动数组
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1e4+5,M=1e7+5;
long long v[N],w[N],f[2][M];
int main(){
int n, m; scanf("%d%d",&m,&n);
for(int i=1; i<=n; i++)
scanf("%d%d",&v[i],&w[i]); //费用,价值
for(int i=1; i<=n; i++) //枚举物品
for(int j=1; j<=m; j++) //枚举体积
if(j<v[i]) f[i&1][j]=f[i-1&1][j];
else f[i&1][j]=max(f[i-1&1][j],f[i&1][j-v[i]]+w[i]);
printf("%lld\n",f[n&1][m]);
}
3)多重背包问题
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int vv[N], ww[N], f[N];
int n, W, v, w, m;
int main()
{
cin >> n >> W;
int idx = 0;
for (int i = 1; i <= n; i++)
{
cin >> v >> w >> m;
for (int j = 1; j <= m; j <<= 1)
{
vv[++idx] = j * v;
ww[idx] = j * w;
m -= j;
}
if (m)
{
vv[++idx] = m * v;
ww[idx] = m * w;
}
}
// 调用01背包解决问题
for (int i = 1; i <= idx; i++)
{
for (int j = W; j >= ww[i]; j--)
{
f[j] = max(f[j], f[j - ww[i]] + vv[i]);
}
}
cout << f[W] << endl;
return 0;
}
5 数位DP
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 12;
int a[N]; // 存储数字的每一位
int f[N][N]; // f[i][j]表示一共有i位,最高位为j时的不降数的个数
int cnt; // 数字的位数
// 处理不降数的个数
void init()
{
for (int i = 0; i <= 9; i++)
f[1][i] = 1; // 一位数
for (int i = 2; i < N; i++) // 阶段:枚举位数
{
for (int j = 0; j <= 9; j++) // 状态:枚举最高位
{
for (int k = j; k <= 9; k++) // 决策:枚举次高位
{
f[i][j] += f[i - 1][k];
}
}
}
}
int dp(int n)
{
if (!n)
return 1; // 特判n==0返回1
int cnt = 0;
while (n)
a[++cnt] = n % 10, n /= 10;
int res = 0, last = 0; // last表示now的高一位
for (int i = cnt; i >= 1; i--)
{
int now = a[i];
for (int j = last; j < now; j++)
res += f[i][j];
if (now < last)
break;
last = now;
if (i == 1)//特判,走到a1的情况
res++;
}
return res;
}
int main()
{
init();
int l, r;
while (cin >> l >> r)
{
cout << dp(r) - dp(l - 1) << endl;
}
return 0;
}
1)windy数
// 数位dp
#include <iostream>
using namespace std;
const int N = 12;
int f[N][10]; // 第i位的最大值为j时的满足条件的数量
int a[N]; // 存储每一位的值
// 初始化
void init()
{
for (int i = 0; i <= 9; i++)
f[1][i] = 1;
for (int i = 2; i < N; i++)
{
for (int j = 0; j <= 9; j++)
{
for (int k = 0; k <= 9; k++)
{
if (abs(k - j) >= 2)
f[i][j] += f[i - 1][k];
}
}
}
}
int dp(int x)
{
if (!x)
return 0;
int cnt = 0;
while (x)
a[++cnt] = x % 10, x /= 10;
int ans = 0, last = -2;
for (int i = cnt; i >= 1; i--)
{
int now = a[i];
for (int j = (i == cnt); j < now; j++) // 最高位从1开始
{
if (abs(j - last) >= 2)
ans += f[i][j];
}
if (abs(now - last) < 2)
break;
last = now;
if (i == 1)
ans++;
}
// 小于cnt位的满足条件的数字
for (int i = 1; i < cnt; i++)
{
for (int j = 1; j <= 9; j++)
{
ans += f[i][j];
}
}
return ans;
}
int main()
{
int l, r;
cin >> l >> r;
init();
cout << dp(r) - dp(l - 1) << endl;
return 0;
}
2)度的数量
/**
* 主要思想为,若满足k个b给整数次方和的数字,则转换成b进制数后,只需看在规定范围内的数字,该进制数可以放k个1的组合数
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 34;
int a[N]; // 记录b机制数
int f[N][N]; // f[i][j],在i个位置上放j个1的组合数
int k, b;
// 初始化组合数
void init()
{
for (int i = 0; i < N; i++)
f[i][0] = 1;
for (int i = 1; i < N; i++)
{
for (int j = 1; j <= i; j++)
{
f[i][j] = f[i - 1][j - 1] + f[i - 1][j];
}
}
}
int dp(int x)
{
if (!x)
return 0;
int cnt = 0;
while (x)
a[++cnt] = x % b, x /= b;
int res = 0, last = 0;
for (int i = cnt; i >= 1; i--)
{
int t = a[i];
if (t)
{
res += f[i - 1][k - last]; // 第i位放0
if (t > 1)
{
if (k - last - 1 >= 0)
res += f[i - 1][k - last - 1]; // 第i位放1
break;
}
else // t==1
{
last++;
if (last > k)
break;
}
}
// 走到末位的情况
if (i == 1 && last == k)
res++;
}
return res;
}
int main()
{
init();
int x, y;
cin >> x >> y;
cin >> k >> b;
cout << dp(y) - dp(x - 1) << endl;
return 0;
}
4 状压DP
1) 小国王
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。
输入格式 只有一行,包含两个数 N,K( 1 ≤ N ≤ 9, 0 ≤ K ≤ N * N) 输出格式 所得的方案数
[Luogu P1896 SCOI2005] 互不侵犯 题目有一定难度,需要好好消化一下
/**
* 状压DP
* 用2进制表示状态,用十进制存储状态(降低空间复杂度)
* 通过位运算筛选出合法的状态
* 用位运算判断状态转移的条件
* 计算时每个类累加 上一行的兼容类
*/
#include <iostream>
using namespace std;
int s[1 << 12]; // 存储状态
int num[1 << 12]; // 存储对应状态的数国王的数量
int cnt; // 同一行合法状态数
long long f[12][144][1 << 12]; // f[i][j][a],第i行j个国王,第i行a状态时的方案数
int main()
{
int n, k;
cin >> n >> k;
for (int i = 0; i < (1 << n); i++)
{
if (!(i & (i >> 1))) // 同一行没有相邻的国王
{
s[cnt++] = i;
// 统计同一行合法状态的国王数量
for (int j = 0; j < n; j++)
num[i] += (i >> j & 1);
}
}
// 边界
f[0][0][0] = 1;
for (int i = 1; i <= n + 1; i++) // 枚举每一行
{
for (int j = 0; j <= k; j++) // 枚举国王数
{
for (int a = 0; a < cnt; a++) // 枚举第i行合法状态
{
int c = num[s[a]]; // 第i行第a个状态的国王数
for (int b = 0; b < cnt; b++) // 枚举第i-1行合法状态
{
if ((j >= c) && !(s[b] & s[a]) // 不存在同列的1
&& !(s[b] & (s[a] >> 1)) // 不存在右上角的1
&& !(s[b] & (s[a] << 1))) // 不存在左上角的1
f[i][j][a] += f[i - 1][j - c][b]; // 行间转移
}
}
}
}
cout << f[n + 1][k][0] << endl;
return 0;
}
2)玉米田
[Luogu P1879 USACO06NOV]Corn Fields(玉米田)
加强判断
#include <iostream>
using namespace std;
const int MOD = 1e8;
int s[1 << 14]; // 保存状态
int f[14][1 << 14]; // 保存方案数
int g[1 << 14]; // 保存每一行的土地情况
int cnt; // 计数
int main()
{
int m, n, x;
cin >> m >> n;
for (int i = 1; i <= m; i++)
{
for (int j = 0; j < n; j++)
{
cin >> x;
g[i] = (g[i] << 1) + x;
}
}
// 找出行内的合法状态
for (int i = 0; i < (1 << n); i++)
{
if (!(i & i >> 1))
s[cnt++] = i;
}
f[0][0] = 1;
for (int i = 1; i <= m + 1; i++) // 枚举每一行
{
for (int a = 0; a < cnt; a++) // 枚举第i行的合法状态
{
for (int b = 0; b < cnt; b++) // 第i-1行的合法状态
{
// 注意运算的先后次序,注意加括号
if ((s[a] & g[i]) == s[a] && (s[b] & g[i - 1]) == s[b] && !(s[a] & s[b]))
f[i][a] = (f[i][a] + f[i - 1][b]) % MOD;
}
}
}
cout << f[m + 1][0] << endl;
return 0;
}
POJ3254 Corn Fields
chapter6 基础算法
1 高精度乘、出、加、减
加
//
#include<iostream>
using namespace std;
const int N=5e10+10;
int a[N],b[N],r[N],n1,n2;
void add(int a[],int b[])
{
if(n1<n2)
{
add(b,a);
}
int c=0;//进位
for(int i=0;i<n2;i++)
{
r[i]=(a[i]+b[i]+c)%10;
c=(a[i]+b[i]+c)/10;
}
if(a[n2])
{
r[n2]=c;
for(int i=n2+1;i<n1;i++)
{
r[i]=a[i];
}
}
for(int i=n1-1;~i;i--)
{
printf("%d",r[i]);
}
}
int main()
{
string a1,a2;//a和b为两个加数
scanf("%s %s",a1,a2);
n1=a1.size();
n2=a2.size();
for(int i=n1-1,k=0;~i;i--,k++)
//~ 是按位取反运算符。当i为0时,~i结果为-1,而-1在计算机中以全1的二进制表示。因此,条件~i 在C++中相当于i != 0
{
a[k]=a1[i];
}
for(int i=n2-1,k=0;~i;i--,k++)
{
b[k]=a2[i];
}
add(a,b);
return 0;
}
#include<iostream>
#include<string>
using namespace std;
const int N=1e500+10;
int a[N],b[N],c[N],len;
string s1,s2;
void add(int a[],int b[])
{
for(int i=0;i<len;i++)
{
c[i]+=a[i]+b[i];//累加
c[i+1]=c[i]/10;//进位
c[i]=c[i]%10;//存入
}
if(c[len])//最高位有进位
{
len++;
}
for(int i=len-1;~i;i--)
{
cout<<c[i];
}
}
int main()
{
cin>>s1>>s2;
if(s1.size()>s2.size())
{
len=s1.size();
}
else
{
len=s2.size();
}
for(int i=s1.size()-1;~i;i--)
{
a[s1.size()-1-i]=s1[i]-'0';
}
for(int i=s2.size()-1;~i;i--)
{
b[s2.size()-1-i]=s2[i]-'0';
}
add(a,b);
return 0;
}
减
乘
#include <iostream>
#include <cstring>
using namespace std;
const int N = 20;
int a[N], b[N], c[N + N];
int size_s1, size_s2, size_c;
void Multiply()
{
for (int i = 0; i < size_s1; i++)
{
for (int j = 0; j < size_s2; j++)
{
c[i + j] += a[i] * b[j]; // 存乘积
c[i + j + 1] += c[i + j] / 10; // 存余数
c[i + j] = c[i + j] % 10; // 存余数
}
}
while (size_c && c[size_c] == 0)
size_c--;
}
int main()
{
// char s1[N],s2[N],r[N*N];
string s1, s2;
cin >> s1 >> s2;
size_s1 = s1.size();
size_s2 = s2.size();
size_c = size_s1 + size_s2;
for (int i = size_s1 - 1; ~i; i--)
{
a[i] = s1[size_s1 - i - 1] - '0';
}
for (int i = size_s2 - 1; ~i; i--)
{
b[i] = s2[size_s2 - i - 1] - '0';
}
Multiply();
for (int i = size_c; ~i; i--)
cout << c[i];
}
除
2 信息学奥赛算法
先序遍历
//先序遍历
void preorder(tree t)
{
if(t)
{
cout<<t->value<<endl;
preorder(t->left);
preorder(t->right);
}
}
中序遍历
void inorder(tree t)
{
if(t)
{
inorder(t->left);
printf("%c",t->value);
inorder(t->right);
}
}
后续遍历及总结
//后序遍历
void postorder(tree t)
{
if(t)
{
postorder(t->left);
postorder(t->right);
printf("%c",t->value);
}
}
//总结
/*
先序遍历:在轨迹的左侧位置
中序遍历:在轨迹的底部位置
后序遍历:在轨迹的右侧位置
*/
推断二叉树
总结:根据后序遍历或先序遍历确定根节点,然后根据中序遍历确定左右子树,最终确定一个唯一的二叉树。但若只知道先序和后序遍历,则无法确定一个唯一的二叉树。
3 二分查找 二分答案
1 整数二分与浮点数二分(其中二分查找是二分答案的基础)
//最大化查找,可行区在左侧
//例如:查找最后一个<=q的数的下标 满足条件可行域发生变化,不满足条件非可行域发生变化
int find(int q)
{
int l=0,r=n+1;//开区间,这里假设下标从1开始,n结束
int mid=l+r>>1;
while(l+1<r)
{
if(a[mid]<=q)
l=mid;
else
r=mid;
}
return l;
}
//最小化查找
int find(int q)
{
int l=0,r=n+1;
int mid=l+r>>1;
while(l+1<r)
{
if(a[mid]>=q)
{
r=mid;
}
else
{
l=mid;
}
}
return r;
}
//若为浮点数
//则需要注意 double mid =(l+r)/2;
(1)题目:洛谷P2249
#include<iostream>
using namespace std;
const int N=1e6+10;
const int M=1e5+10;
int a[N],n,c[M],m;
int find(int q)
{
int l=-1,r=n+1;
while(l+1<r)
{
int mid=l+r>>1;
if(a[mid]>=q)
{
r=mid;
}
else
{
l=mid;
}
}
return a[r]==q ? r+1:-1;
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
}
for(int i=0;i<m;i++)
{
scanf("%d",&c[i]);
}
for(int i=0;i<m;i++)
{
printf("%d ",find(c[i]));
}
}
(2) 题目:luoguP1024
2 二分答案
P2440 木材加工
/*
这里是典型的最大化答案问题。
题目要求找出最大的l,并满足要求的切割后的数目
**可以将l(段长)的值作为x,y作为段数,容易直到随着段长x的增加,段数会减少。
可行域再左侧,当满足段长时,向右收缩
不满足时,向左收缩。
*/
#include<iostream>
using namespace std;
const int N=1e5+10;
int a[N],n,k;//用于存放原木长度
bool check(int x)
{
int y=0;
for(int i=1;i<=n;i++)
{
y+=a[i]/x;
}
return y>=k;
}
int find()
{
int l=0,r=1e8+10;
while(l+1<r)
{
int mid=l+r>>1;
if(check(mid))
{
l=mid;
}
else
{
r=mid;
}
}
return l;
}
int main()
{
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
int l=find();
printf("%d",l);
}
P2678跳石头
/*
*/
#include<iostream>
using namespace std;
const int N=5e4+10;
int a[N],n,l,m;
//随着最短距离的增大,势必会使得移走的岩石数增多
bool check(int x)
{
int y=0,last=0;
for(int i=1;i<=n+1;i++)
{
if((a[i]-a[last])<x)
{
y++;
}
else
{
last=i;
}
}
return y<=m;
}
int find()
{
int l=0,r=1e9+1;
while(l+1<r)
{
int mid=l+r>>1;
if(check(mid))
{
l=mid;
}
else
{
r=mid;
}
}
return l;
}
int main()
{
scanf("%d %d %d",&l,&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
a[n+1]=l;//右边界
int l=find();
printf("%d",l);
}
P1314 聪明的质检员
/*
*/
4 分数规划(见day4)
1POJ2976 Dropping tests
//最大化问题
#include<iostream>
#include<algorithm>
using namespace std;
const int N=10010;
double a[N],b[N],c[N];
int n,k;
bool check(double x)
{
double s=0;
for(int i=1;i<=n;i++)
{
c[i]=a[i]-x*b[i];
}
sort(c+1,c+n+1);//升序排列
for(int i=k+1;i<=n;i++)
{
s+=c[i];
}
return s>=0;
}
//最大化,当check为真时,l右移
double find()
{
double l=0,r=1;
while(r-l>1e-4)//结果会保留两位小数后进行*100操作,因此需要将区间划分到1e-4
{
double mid=(l+r)/2;
if(check(mid))
{
l=mid;
}
else
{
r=mid;
}
}
return l;
}
int main()
{
while(scanf("%d%d",&n,&k),n)
{
for(int i=1;i<=n;i++)
{
scanf("%lf",&a[i]);
}
for(int i=1;i<=n;i++)
{
scanf("%lf",&b[i]);
}
printf("%.0lf\n",100*find());
}
return 0;
}
2 [Luogu P4377 USACO18OPEN] Talent Show G
//这里用到01背包
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(j<w[i])
{
f[i][j]=f[i-1][j];
}
else
{
max(f[i-1][j],f[i-1][j-w[i]]+c[i]);//这里的c表示价值
}
}
}
//通过简化板子,降低空间复杂度
f[0]=0;
for(int i=1;i<=n;i++)
{
for(int j=m;j>=w[i];j--)
{
f[j]=max(f[j],f[j-w[i]]+c[i]);
}
}
printf("%d",f[m]);
-------------------------------------------------------------------------------------------------
#include<iostream>
#include<algorithm>
using namespace std;
const int N=255;
int n,W,w[N],t[N];
double f[1005];
bool check(double x)
{
for(int i=1;i<=W;i++)
{
f[i]=-1e9;
}
for(int i=1;i<=n;i++)
{
for(int j=W;j>=0;j--)
{
int k=min(W,j+w[i]);
f[k]=max(f[k],f[j]+t[i]-x*w[i]);
}
}
return f[W]>=0;
}
double find()
{
double l=0,r=1000;
while(r-l>1e-5)
{
double mid=(l+r)/2;
if(check(mid))
l=mid;
else
r=mid;
}
return r;//为了达到*1000后向下取整
}
int main()
{
// f[0]=0;
scanf("%d%d",&n,&W);
for(int i=1;i<=n;i++)
{
scanf("%d%d",&w[i],&t[i]);
}
printf("%d",int(find()*1000));
return 0;
}
5 前缀和二维前缀和
1)一维前缀和
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int s[N], a[N]; // s存前i项的和,a存数列
int n, m, l, r;
int main()
{
cin >> n;
s[0] = 0;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
s[i] = s[i - 1] + a[i];
}
cin >> m;
for (int j = 0; j < m; j++)
{
cin >> l >> r;
cout << s[r] - s[l - 1] << endl;
}
return 0;
}
2)二维前缀和
[Luogu P2280 HNOI2003] 激光炸弹
/**
* 二维前缀和
* 基于容斥原理
* 难点:两个公式 s[i][j]=s[i][j-1]+s[i-1][j]-s[i-1]s[j-1]+a[i][j];
* 一段区间的和:sum=s[i][j]-s[a-1][j]-s[i][b-1]+s[a-1][b-1];
* (a,b)为区间左上方顶点,(i,j)为区间右下方顶点
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
int ad[N][N], s[N][N];
int n, m, x, y, v;
int main()
{
cin >> n >> m;
// s[0][0]=0;
for (int i = 1; i <= n; i++)
{
cin >> x >> y >> v;
x++, y++; // 让坐标从(1,1)开始
ad[x][y] += v;
}
for (int i = 1; i <= 5010; i++)
{
for (int j = 1; j <= 5010; j++)
{
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + ad[i][j];
}
}
int res = 0;
for (int x = m; x < 5010; x++)
{
for (int y = m; y < 5010; y++)
{
res = max(res, s[x][y] - s[x - m][y] - s[x][y - m] + s[x - m][y - m]);
}
}
cout << res << endl;
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N = 103;
int n, m;
int s[N][N], a[N][N];
/**
* 这里的边长为x
*/
bool check(int x)
{
for (int i = x; i <= n; i++)
{
for (int j = x; j <= m; j++)
{
int y = s[i][j] - s[i - x][j] - s[i][j - x] + s[i - x][j - x];
if (y == x * x)
return true;
}
}
return false;
}
int find()
{
int l = 1, r = min(n, m) + 1;
while (l + 1 < r)
{
int mid = l + r >> 1;
if (check(mid))
l = mid;
else
r = mid;
}
return l;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
cin >> a[i][j];
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
}
}
int ans = find();
cout << ans << endl;
return 0;
}
3)树上前缀和(理解不深)
可利用Tarjan(塔扬算法),复习巩固,以及掌握倍增法求公共祖先。以此为前提掌握树上前缀和
[Luogu P4427 BJOI2018] 求和
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 3e5 + 10;
const int M = N * 2;
const LL mod = 998244353;
struct edge
{
int v, ne;
};
int i, j, k, n, m, idx;
edge e[M]; // 保存结点信息
int fa[N][22], h[N], dep[N];
LL mi[60], s[N][60]; // mi[j]保存dep[v]的j次幂,s[u][j]表示从根节点到u的深度的j次幂之和
void add(int a, int b)
{
e[++idx] = {b, h[a]};
h[a] = idx;
}
void dfs(int u, int f)
{
for (int i = 0; i <= 19; i++)
{
fa[u][i + 1] = fa[fa[u][i]][i];
}
for (int i = h[u]; i; i = e[i].ne)
{
int v = e[i].v;
if (v == f)
continue;
fa[v][0] = u;
dep[v] = dep[u] + 1;
for (int i = 1; i <= 50; i++)
mi[i] = mi[i - 1] * dep[v] % mod;
for (int i = 1; i <= 50; i++)
{
s[v][i] = (s[u][i] + mi[i]) % mod;
}
dfs(v, u);
}
}
int lca(int x, int y)
{
if (dep[x] < dep[y])
swap(x, y);
//
for (int i = 19; i >= 0; i--)
{
if (dep[fa[x][i]] >= dep[y])
x = fa[x][i];
}
if (x == y)
return x;
for (int i = 19; i >= 0; i--)
{
if (fa[x][i] != fa[y][i])
x = fa[x][i], y = fa[y][i];
}
return fa[x][0];
}
int main()
{
cin >> n;
for (int a = 1; a <= n - 1; a++)
{
cin >> i >> j;
add(i, j);
add(j, i);
}
mi[0] = 1;
dfs(1, 0);
cin >> m;
for (int b = 1; b <= m; b++)
{
cin >> i >> j >> k;
int l = lca(i, j);
// cout << l << endl;
LL ans = (s[i][k] + s[j][k] - s[l][k] - s[fa[l][0]][k] + 2 * mod) % mod;
cout << ans << endl;
}
return 0;
}
6 差分
1)一维差分
注意结论的推导
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
LL a[N], b[N];
int n;
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
// 建立差分数组
for (int i = 1; i <= n; i++)
{
b[i] = a[i] - a[i - 1];
}
LL p = 0, q = 0;
for (int i = 2; i <= n; i++)
{
if (b[i] > 0)
p += b[i];
else
q += abs(b[i]);
}
cout << max(p, q) << "\n"
<< abs(p - q) + 1 << endl;
}
2)二维差分
#include <iostream>
using namespace std;
const int N = 1010;
int n, m, x1, y, x2, y2;
int a[N][N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
cin >> x1 >> y >> x2 >> y2;
// 进行差分
for (int j = x1; j <= x2; j++)
{
a[j][y]++;
a[j][y2 + 1]--;
}
}
// 进行前缀和
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
a[i][j] += a[i][j - 1];
cout << a[i][j] << " ";
}
cout << endl;
}
return 0;
}
3)树上差分
[Luogu P3128 USACO15DEC] Max Flow P
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
const int M = 2 * N;
struct edge
{
int to, ne;
} e[M];
int idx, n, k, x, y, s, t;
int h[N], fa[N][22];
int dep[N];
int power[N]; // 存储子节点的和
int ans; // 存储最大值
void add(int a, int b)
{
e[++idx] = {b, h[a]};
h[a] = idx;
}
/**
* 倍增法求最近总共祖先
*
*/
void dfs(int u, int f)
{
dep[u] = dep[f] + 1;
fa[u][0] = f;
for (int i = 0; i < 21; i++)//一定要注意数组的下标,防止越界
{
fa[u][i + 1] = fa[fa[u][i]][i];
}
for (int i = h[u]; i; i = e[i].ne)
{
if (e[i].to != f)
dfs(e[i].to, u);
}
}
int lca(int x, int y)
{
if (dep[x] < dep[y])
swap(x, y);
// 先跳到同一深度
for (int i = 21; i >= 0; i--)
{
if (dep[fa[x][i]] >= dep[y])
x = fa[x][i];
}
if (x == y)
return x;
// 再调到公共结点的下一层
for (int i = 21; i >= 0; i--)
{
if (fa[x][i] != fa[y][i])
{
x = fa[x][i], y = fa[y][i];
}
}
return fa[x][0];
}
void dfs2(int u, int f)
{
for (int i = h[u]; i; i = e[i].ne)
{
int v = e[i].to;
if (v == f)
continue;
dfs2(v, u);
power[u] += power[v];
}
ans = max(ans, power[u]);
}
int main()
{
cin >> n >> k;
for (int i = 0; i < n - 1; i++)
{
cin >> x >> y;
add(x, y);
add(y, x);
}
dfs(1, 0);
for (int j = 0; j < k; j++)
{
cin >> s >> t;
// 进行树上差分
int l = lca(s, t);
++power[s], ++power[t];
--power[l], --power[fa[l][0]];
}
dfs2(1, 0);
cout << ans << endl;
}
[Luogu P3258 JLOI2014] 松鼠的新家
#include <iostream>
using namespace std;
const int N = 3e5 + 10;
const int M = 2 * N;
struct edge
{
int to, ne;
} e[M];
int h[N], idx;
int fa[N][31], dep[N];
int a[N], x, y, n, t;
int ans[N]; // 记录走过的次数
void add(int a, int b)
{
e[++idx] = {b, h[a]};
h[a] = idx;
}
void dfs(int u, int f)
{
dep[u] = dep[f] + 1;
fa[u][0] = f;
for (int i = 0; i < 30; i++)
{
fa[u][i + 1] = fa[fa[u][i]][i];
}
for (int i = h[u]; i; i = e[i].ne)
{
if (e[i].to != f)
{
dfs(e[i].to, u);
}
}
}
int lca(int x, int y)
{
if (dep[x] < dep[y])
swap(x, y);
for (int i = 30; i >= 0; i--)
{
if (dep[fa[x][i]] >= dep[y])
{
x = fa[x][i];
}
}
if (x == y)
return x;
for (int j = 30; j >= 0; j--)
{
if (fa[x][j] != fa[y][j])
{
x = fa[x][j], y = fa[y][j];
}
}
return fa[x][0];
}
void dfs2(int u, int f)
{
for (int i = h[u]; i; i = e[i].ne)
{
int v = e[i].to;
if (v == f)
continue;
dfs2(v, u);
ans[u] += ans[v];
}
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
// cin >> a[i];
scanf("%d", &a[i]);
}
for (int j = 1; j <= n - 1; j++)
{
// cin >> x >> y;
scanf("%d%d", &x, &y);
add(x, y);
add(y, x);
}
dfs(1, 0);
for (int k = 1; k <= n - 1; k++)
{
int x = a[k], y = a[k + 1];
int l = lca(x, y);
++ans[x], ++ans[y];
--ans[l], --ans[fa[l][0]];
}
dfs2(1, 0);
for (int i = 1; i <= n; i++)
{
//这里要尤其注意,对题意要充分理解
if (i == a[1])
// cout << ans[i] << endl;
printf("%d\n", ans[i]);
else
// cout << ans[i] - 1 << endl;
printf("%d\n", ans[i] - 1);
}
return 0;
}
[Luogu P2680 NOIP2015 提高组] 运输计划
需要自己手敲一下
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=300010;
struct node{
int to,next,w;
}edge[maxn*2];
struct no{
int u,v,lcaa,diss;
}lu[maxn*2];
int summ,cnt=0,k,n,m,num[maxn],mi[maxn],vis[maxn];
int temp[maxn],head[maxn],deep[maxn],dis[maxn];
int fa[maxn][25],dp[maxn][25];
void adde(int u,int v,int w){
k++;
edge[k].to=v;
edge[k].next=head[u];
edge[k].w=w;
head[u]=k;
}
void dfs(int x,int pa,int dep){
cnt++;
num[cnt]=x;
deep[x]=dep;
vis[x]=1;
for(int i=1;i<25;i++){
fa[x][i]=fa[fa[x][i-1]][i-1];
}
for(int i=head[x];i>0;i=edge[i].next){
int v=edge[i].to;
if(!vis[v]){
fa[v][0]=x;
dis[v]=dis[x]+edge[i].w;
dfs(v,x,dep+1);
}
}
}
int lca(int x,int y){
if(deep[x]<deep[y]) swap(x,y);
int t=deep[x]-deep[y];
for(int i=0;i<25;i++){
if((1<<i)&t) x=fa[x][i];
}
if(x==y) return x;
for(int i=24;i>=0;i--){
if(fa[x][i]!=fa[y][i]){
x=fa[x][i];y=fa[y][i];
}
}
return fa[x][0];
}
bool check(int mid){
int cnt=0,ans=0;
memset(temp,0,sizeof(temp));
for(int i=1;i<=m;i++){
if(lu[i].diss>mid){
temp[lu[i].u]++;temp[lu[i].v]++;temp[lu[i].lcaa]-=2;
ans=max(ans,lu[i].diss-mid);
cnt++;
}
}
if(cnt==0) return true;
for(int i=n;i>=1;i--) temp[fa[num[i]][0]]+=temp[num[i]];
for(int i=2;i<=n;i++) if(temp[i]==cnt&&dis[i]-dis[fa[i][0]]>=ans) return true;
return false;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n-1;i++){
int x,y,w;
scanf("%d%d%d",&x,&y,&w);
adde(x,y,w);adde(y,x,w);
summ+=w;
}
dis[1]=0;
dfs(1,0,1);
for(int i=1;i<=m;i++){
scanf("%d%d",&lu[i].u,&lu[i].v);
lu[i].lcaa=lca(lu[i].u,lu[i].v);
lu[i].diss=dis[lu[i].u]+dis[lu[i].v]-2*dis[lu[i].lcaa];
}
int left=0,right=summ;
int mid;
while(left<right){
mid=(left+right)>>1;
if(check(mid)) right=mid;
else left=mid+1;
}
printf("%d",left);
return 0;
}
[Luogu P1600 NOIP2016 提高组] 天天爱跑步
[Luogu P4556 Vani有约会] 雨天的尾巴
7 ST表 RMQ问题
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 1e5 + 10;
int f[N][22], m, n, t, l, r;
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9')
{
if (ch == '-')
f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
int main()
{
// scanf("%d%d", &n, &m);
n = read(), m = read();
for (int i = 1; i <= n; i++)
// scanf("%d", &f[i][0]);
f[i][0] = read();
// ST表初始化
for (int j = 1; j <= 20; j++)
{
for (int i = 1; i + (1 << j) - 1 <= n; i++)
{
f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}
}
// 计算l-r区间内的最大值
for (int j = 1; j <= m; j++)
{
// scanf("%d%d", &l, &r);
l = read(), r = read();
int k = log2(r - l + 1);
printf("%d\n", max(f[l][k], f[r - (1 << k) + 1][k]));//cout耗时久
}
return 0;
}
[Luogu P1816 忠诚](https://www.luogu.com.cn/problem/P18
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int m, n, f[N][17], l, r;
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9')
{
if (ch == '-')
f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
int main()
{
m = read(), n = read();
for (int i = 1; i <= m; i++)
{
f[i][0] = read();
}
// 初始化ST表
for (int j = 1; j <= 16; j++)
{
for (int i = 1; i + (1 << j) - 1 <= m; i++)
{
f[i][j] = min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}
}
for (int i = 1; i <= n; i++)
{
l = read(), r = read();
int k = log2(r - l + 1);
printf("%d ", min(f[l][k], f[r - (1 << k) + 1][k]));//一定要注意要求输出的格式
}
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int f[N][22], n, m;
// 预处理ST表
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9')
{
if (ch == '-')
{
f = -1;
}
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void init_ST(int n)
{
for (int j = 1; j <= 20; j++)
{
for (int i = 1; i + (1 << j) - 1 <= n; i++)
{
f[i][j] = min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}
}
}
int main()
{
n = read(), m = read();
for (int i = 1; i <= n; i++)
{
f[i][0] = read();
}
init_ST(n);
for (int i = 1; i <= (n - m + 1); i++)
{
int l = i, r = i + m - 1;
int k = log2(r - l + 1);
printf("%d\n", min(f[l][k], f[r - (1 << k) + 1][k]));
}
return 0;
}
[Luogu P2880 USACO07JAN] Balanced Lineup G
#include <bits/stdc++.h>
using namespace std;
const int N = 5e4 + 10;
int f1[N][22], f2[N][22], n, q, a, b;
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while (ch > '9' || ch < '0')
{
if (ch == '-')
f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void init_ST(int n)
{
for (int j = 1; j <= 20; j++)
{
for (int i = 1; i + (1 << j) - 1 <= n; i++)
{
f1[i][j] = max(f1[i][j - 1], f1[i + (1 << (j - 1))][j - 1]);
f2[i][j] = min(f2[i][j - 1], f2[i + (1 << (j - 1))][j - 1]);
}
}
}
int main()
{
n = read(), q = read();
for (int i = 1; i <= n; i++)
{
f1[i][0] = f2[i][0] = read();
}
init_ST(n);
for (int i = 1; i <= q; i++)
{
a = read(), b = read();
int k = log2(b - a + 1);
printf("%d\n", max(f1[a][k], f1[b - (1 << k) + 1][k]) - min(f2[a][k], f2[b - (1 << k) + 1][k]));
}
return 0;
}
8 贪心算法
1.[Luogu P1090 NOIP2004 提高组] 合并果子
#include <iostream>
#include <queue>
using namespace std;
const int N = 1e4 + 10;
int n;
// 使用小根堆
priority_queue<int, vector<int>, greater<int>> q;
int ans; // 保存所消耗的体力之和
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
int a;
cin >> a;
q.push(a);
}
while (q.size() > 1)
{
int a = q.top();
q.pop();
int b = q.top();
q.pop();
ans += a + b;
q.push(a + b);
}
cout << ans << endl;
return 0;
}
2. POJ 3617 Best Cow Line
/**
* 解题思路
* 1.不断地比较a开头和结尾的字符的大小,将小的输出,若在开头指针加1
* 若在结尾,指针减1.
*/
#include <iostream>
using namespace std;
const int N = 2010;
int n;
char a[N];
void solve()
{
int s = 1, e = n;
int ant = 0;
while (s <= e)
{
bool flag = false;
for (int i = 0; s + i <= e; i++)
{
if (a[s + i] < a[e - i])
{
flag = true;
break;
}
else if (a[s + i] > a[e - i])
{
flag = false;
break;
}
}
ant++;
if (flag)
putchar(a[s++]);
else
putchar(a[e--]);
if (ant % 80 == 0)
putchar('\n');
}
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
solve();
return 0;
}
3. POJ 3069 Saruman Army
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int r, n; // r表示距离,n表示点的个数
int a[N];
/**
* 注意:这里的距离范围包括前后,所以一个点找出后,还得继续向右找到下一个开始点
*/
void solve()
{
int ans = 0;
sort(a, a + n);
int k = 0;
while (k < n)
{
//s是没有别覆盖的最做的点的位置
int s = a[k++];
//一直向右前进到距s大于r的点
while (k < n && a[k] <= s + r)
k++;
//p是新标记的点的位置
int p = a[k - 1];
//一直向右前进到距p大于r的点
while (k < n && a[k] <= p + r)
k++;
ans++;
}
cout << ans << endl;
}
int main()
{
while (1)
{
cin >> r >> n;
if (r == -1 && n == -1)
break;
for (int i = 0; i < n; i++)
cin >> a[i];
solve();
}
return 0;
}
4. POJ 3253 Fence Repair
//solve1:时间复杂度O(NlogN)
//简简单单一个小根堆解决
// 使用默认的比较函数(从大到小排序)std::priority_queue<int, std::vector<int>, std::greater<int>> q;
//在C++的标准库中,priority_queue 默认使用 std::less 作为比较函数,这会导致堆顶元素是最大值,因此它是一个大根堆。
#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
typedef long long ll;
int n;
ll ans;
int x;
priority_queue<int, vector<int>, greater<int>> q;
void solve()
{
while (q.size() - 1)
{
int x = q.top();
q.pop();
int y = q.top();
q.pop();
ans += x + y;
q.push(x + y);
}
cout << ans << endl;
}
int main()
{
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> x;
q.push(x);
}
solve();
return 0;
}
//solve2:时间复杂度O(n^2)
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 2e4 + 10;
int n, l[N];
ll ans;
void solve()
{
while (n > 1)
{
int mi1 = 0, mi2 = 1;
if (l[mi1] > l[mi2])
swap(mi1, mi2);
for (int i = 2; i < n; i++)
{
if (l[i] < l[mi1])
{
mi2 = mi1;
mi1 = i;
}
else if (l[i] < l[mi2])
{
mi2 = i;
}
}
// 将两块板子拼起
int t = l[mi1] + l[mi2];
ans += t;
if (mi1 == n - 1)
swap(mi1, mi2);
l[mi1] = t;
l[mi2] = l[n - 1];
n--;
}
cout << ans << endl;
}
int main()
{
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> l[i];
}
solve();
return 0;
}
chapter7 字符串
1 最小表示法
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 3e5 + 10;
int n, a[N + N];
int get_min()
{
for (int i = 1; i <= n; i++)
{
a[n + i] = a[i];
}
int i = 1, j = 2, k = 0;
while (i <= n && j <= n)
{
for (k = 0; k < n && a[i + k] == a[j + k]; k++);
a[i + k] > a[j + k] ? i = i + k + 1 : j = j + k + 1;
if (i == j)
j++;
}
return min(i, j);
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
int min = get_min();
for (int i = 1; i <= n; i++)
{
cout << a[min + i - 1] << " ";
}
return 0;
}
2 字符串哈希(字符串)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef unsigned long long ULL;
const int N = 1e4 + 10;
const int P = 131; // 或是133331
ULL p[N], h[N], ans[N]; // 这里的ans存储每个串的哈希值
char s[N];
int n;
// 计算串的hash值
ULL init(char *s, int n)
{
h[0] = 0, p[0] = 1; // 这里的保存前缀和,这里的p保存P的平方值
for (int i = 1; i <= n; i++)
{
p[i] = p[i - 1] * P;
h[i] = h[i - 1] * P + s[i];
}
return h[n];
}
int main()
{
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> s + 1;
int len = strlen(s + 1);
ans[i] = init(s, len);
}
sort(ans, ans + n);
int t = n;
for (int i = 0; i < n - 1; i++)
{
if (ans[i] == ans[i + 1])
{
t--;
}
}
cout << t << endl;
return 0;
}
注:
//字符串哈希还可以进行比较某一区间内的字符串是否相等
ULL get_s(int l,int r)
{
return x1=h[r]-h[l-1]*p[r-l+1];
}
3 KMP算法
//计算相等前后缀的最长长度 p(长度为n)为s(长度为m)的子串
/**
* 模板
*/
ne[1]=0;
for(int i=2,j=0;i<=n;i++)
{
while(j&&p[i]!=p[j+1])
j=ne[j];
if(p[i]==p[j+1])
j++;
ne[i]=j;
}
//求下标
for(int i=1,j=0;i<=m;i++)
{
while(j&&s[i]!=p[j+1])
j=ne[j];
if(s[i]==p[j+1])
j++;
if(j==n)
cout<< (i-n+1)<<endl;
}
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
char s1[N], s2[N];
int ne[N];
// string s1,s2;
int len1, len2;
int main()
{
cin >> s1 + 1 >> s2 + 1;
len1 = strlen(s1 + 1), len2 = strlen(s2 + 1);
// 求相等前后缀的最长长度
ne[1] = 0;
for (int i = 2, j = 0; i <= len2; i++)
{
while (j && s2[i] != s2[j + 1])
j = ne[j];
if (s2[i] == s2[j + 1])
j++;
ne[i] = j;
}
// 计算位置
for (int i = 1, j = 0; i <= len1; i++)
{
while (j && s1[i] != s2[j + 1])
j = ne[j];
if (s1[i] == s2[j + 1])
j++;
if (j == len2)
cout << (i - len2 + 1) << endl;
}
for (int i = 1; i <= len2; i++)
cout << ne[i] << " ";
return 0;
}
4 扩展KMP(z函数)
#include <bits/stdc++.h>
using namespace std;
const int N = 2e7+10;
int z[N], p[N];
char a[N], b[N];
// 计算z函数
void KMP_z(char *s, int n)
{
z[1] = n; // b与b的每一个后缀的LCP长度
for (int i = 2, l, r = 0; i <= n; i++)
{
// 判断是否在盒内
if (i <= r)
z[i] = min(z[i - l + 1], r - i + 1);
// 不在盒内就进行暴力枚举
while (s[1 + z[i]] == s[i + z[i]])
z[i]++;
// 改变区间
if (i + z[i] - 1 > r)
l = i, r = i + z[i] - 1;
}
}
// p[N]--> b与a的每一个后缀的LCP长度数组,其长度为m
void kmp_p(char *a, int n, int m)
{
for (int i = 1, l, r = 0; i <= m; i++)
{
if (i <= r)
p[i] = min(z[i - l + 1], r - i + 1);
while (b[1 + p[i]] == a[i + p[i]] && 1 + p[i] <= n && i + p[i] <= m)
p[i]++;
if (i + p[i] - 1 > r)
l = i, r = i + p[i] - 1;
}
}
// 按公式计算
void calculate(int a[], int n)
{
long long ans = 0;
for (int i = 1; i <= n; i++)
{
ans ^= 1LL * i * (a[i] + 1);
}
cout << ans << endl;
}
int main()
{
cin >> a + 1 >> b + 1;
int len1 = strlen(a + 1);
int len2 = strlen(b + 1);
KMP_z(b, len2);
kmp_p(a, len2, len1);
calculate(z, len2);
calculate(p, len1);
return 0;
}
5 Manacher算法
#include <bits/stdc++.h>
using namespace std;
const int N = 3e7;
int d[N]; // 记录回文半径
char s[N], t[N]; // s为输入的串,t为改造后的串
void get_d(char *t, int n)
{
d[1] = 1;
for (int i = 2, l, r = 1; i <= n; i++)
{
if (i <= r)
d[i] = min(d[r - i + l], r - i + 1);//一定要注意边界【r-i+l】
while (t[i - d[i]] == t[i + d[i]])
d[i]++;
if (i + d[i] - 1 > r)
l = i - d[i] + 1, r = i + d[i] - 1;
}
}
int main()
{
cin >> s + 1;
int n = strlen(s + 1);
int k = 0;
t[0] = '$', t[++k] = '#';
// 改造输入的字符串
for (int i = 1; i <= n; i++)
{
t[++k] = s[i], t[++k] = '#';
}
n = k;
// 计算d函数
get_d(t, n);
int ans = 0;
for (int i = 1; i <= n; i++)
{
ans = max(ans, d[i]);
}
cout << ans - 1 << endl;
return 0;
}
chapter8 搜索
2 图的存储
//链式临接表
//可以反向
// 链式临接表
// 可以反向
#include <iostream>
#include <vector>
using namespace std;
const int N = 1005;
int n, m, u, v, w;
/**
* 用于存储图的信息
*/
struct edge
{
int u, v, w;
};
vector<edge> e;
/**
* 用于存储出边信息
*/
vector<int> h[N];
/**
* 存储边的信息函数
*/
void add(int a, int b, int c)
{
e.push_back({a, b, c});
h[a].push_back(e.size() - 1);
}
/**
* fa为父节点
* u为开始查找的点
*/
void dfs(int u, int fa)
{
// 先找边的序号
for (int i = 0; i < h[u].size(); i++)
{
int j = h[u][i];
int v = e[j].v, w = e[j].w;
if (v == fa)
continue;
cout << u << " " << v << " " << w << endl;
dfs(v, u);
}
}
int main()
{
cin >> n >> m;
for (int i = 0; i < m; i++)
{
cin >> u >> v >> w;
add(u, v, w);
add(v, u, w);
}
dfs(1, 0);
return 0;
}
///邻接矩阵 示例
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1010,M=1010;
int n,m,a,b,c;
int w[N][N];//边权
int vis[N];
void dfs(int u){
vis[u]=true;
for(int v=1;v<=n;v++)
if(w[u][v]){
printf("%d,%d,%d\n",u,v,w[u][v]);
if(vis[v]) continue;
dfs(v);
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>a>>b>>c;
w[a][b]=c;
// w[b][a]=c;
}
dfs(1);
return 0;
}
///边集数组 示例
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1010,M=1010;
int n,m,a,b,c;
struct edge{
int u,v,w;
}e[M];//边集
int vis[N];
void dfs(int u){
vis[u]=true;
for(int i=1;i<=m;i++)
if(e[i].u==u){
int v=e[i].v,w=e[i].w;
printf("%d,%d,%d\n",u,v,w);
if(vis[v]) continue;
dfs(e[i].v);
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>a>>b>>c;
e[i]={a,b,c};
// e[i]={b,a,c};
}
dfs(1);
return 0;
}
///邻接表 示例
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N=510;
int n,m,a,b,c;
struct edge{int v,w;};
vector<edge> e[N];//边集
void dfs(int u,int fa){
for(auto ed : e[u]){
int v=ed.v, w=ed.w;
if(v==fa) continue;
printf("%d,%d,%d\n",u,v,w);
dfs(v, u);
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>a>>b>>c,
e[a].push_back({b,c});
// e[b].push_back({a,c});
}
dfs(1, 0);
return 0;
}
///链式邻接表 示例
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N=510;
int n,m,a,b,c;
struct edge{int u,v,w;};
vector<edge> e;//边集
vector<int> h[N];//点的所有出边
void add(int a,int b,int c){
e.push_back({a,b,c});
h[a].push_back(e.size()-1);
}
void dfs(int u,int fa){
for(int i=0;i<h[u].size();i++){
int j=h[u][i];
int v=e[j].v,w=e[j].w;
if(v==fa) continue;
printf("%d,%d,%d\n",u,v,w);
dfs(v,u);
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>a>>b>>c,
add(a,b,c);
add(b,a,c);
}
dfs(1, 0);
return 0;
}
///链式前向星 示例
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N=510,M=3000;
int n,m,a,b,c;
struct edge{int v,w,ne;};
edge e[M];//边集
int idx,h[N];//点的第一条出边
void add(int a,int b,int c){
e[idx]={b,c,h[a]};
h[a]=idx++;
}
void dfs(int u,int fa){
for(int i=h[u];~i;i=e[i].ne){
int v=e[i].v, w=e[i].w;
if(v==fa) continue;
printf("%d,%d,%d\n",u,v,w);
dfs(v,u);
}
}
int main(){
cin>>n>>m;
memset(h,-1,sizeof h);
for(int i=1;i<=m;i++){
cin>>a>>b>>c,
add(a,b,c);
add(b,a,c);
}
dfs(1, 0);
return 0;
}
3 深搜(DFS)算法
#include <iostream>
#include <vector>
using namespace std;
const int N = 1000;
// 利用邻接表存边的信息
vector<int> e[N];
int n, m, a, b;
void dfs(int a, int fa)
{
for (auto t : e[a])
{
if (fa == t)
continue;
// cout<<"下走"
dfs(t, a);
// 上回
}
// 离开
}
int main()
{
cin >> n >> m;
for (int i = 0; i < m; i++)
{
cin >> a >> b;
e[a].push_back(b);
e[b].push_back(a);
}
dfs(1, 0);
return 0;
}
例题1 Luogu P1605 迷宫
#include <iostream>
using namespace std;
const int N = 20;
// 上右下左的顺序
const int ax[4] = {0, -1, 0, 1};
const int ay[4] = {1, 0, -1, 0};
int n, m, t, sx, sy, fx, fy;
int ans = 0;
int g[N][N]; // 存储障碍信息
// 深搜
void dfs(int x, int y)
{
if (x == fx && y == fy)
{
ans++;
return;
}
for (int i = 0; i < 4; i++)
{
int a = x + ax[i], b = y + ay[i];
if (a < 1 || a > n || b < 1 || b > m || g[a][b])
continue;
g[a][b] = 1;
dfs(a, b);
g[a][b] = 0;
}
}
int main()
{
cin >> n >> m >> t;
cin >> sx >> sy >> fx >> fy;
for (int i = 0; i < t; i++)
{
int x, y;
cin >> x >> y;
g[x][y] = 1;
}
g[sx][sy] = 1;
dfs(sx, sy);
cout << ans << endl;
return 0;
}
例题2 P1644 跳马问题
#include <bits/stdc++.h>
using namespace std;
const int N = 18;
const int ax[] = {2, 2, 1, 1};
const int ay[] = {1, -1, 2, -2};
int n, m, ans = 0;
void dfs(int x, int y)
{
if (x == m && y == n)
{
ans++;
return;
}
for (int i = 0; i < 4; i++)
{
int a = x + ax[i], b = y + ay[i];
if (a > m || b > n || b < 0)
continue;
dfs(a, b);
}
}
int main()
{
cin >> n >> m;
dfs(0, 0);
cout << ans << endl;
return 0;
}
例题3 [Luogu P1219 USACO1.5]八皇后
/**
* 注意找规律
* 行关系 row 每次放隔一行放
* 列关系 col 当可以放后,讲这一列标记为1
* 主对角线关系 row+col相同
* 副对角线关系 row-col相同 为了避免出现负数,给结果加一个n
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 23;
int n, ans, sum;
int g[N][N]; // 存储是否被访问
int pos[N], col[N], ma[N], sub[N]; // 其中ma为主对角线,sub为副对角线
void dfs(int x)
{
if (x > n)
{
ans++;
if (ans <= 3)
{
for (int i = 1; i <= n; i++)
cout << pos[i] << " ";
cout << endl;
}
return;
}
for (int j = 1; j <= n; j++)
{
if (col[j] || ma[x + j] || sub[x - j + n])
{
continue;
}
pos[x] = j;
col[j] = 1, ma[x + j] = 1, sub[x - j + n] = 1;
dfs(x + 1);
col[j] = 0, ma[x + j] = 0, sub[x - j + n] = 0;
}
}
int main()
{
cin >> n;
dfs(1);
cout << ans << endl;
return 0;
}
4 dfs树的重心
#include <bits/stdc++.h>
using namespace std;
/**
* 树的重心
* 用size记录以u为根节点的树的结点数
* mx记录u的上方和下方的最大值
* TODO:树的重心
* 重心是指树种的一个结点,如果将这个结点删除后,剩余各个联通块中点数的最大值最小
* 那么这个结点被称为树的重心。
*/
const int N = 1e6 + 10;
vector<int> e[N]; // 用邻接表存储树
int n, a, b;
int size[N], ans = 1e9, pos; // pos记录重心
void DFS(int x, int fa)
{
size[x] = 1;
int mx = 0;
for (auto t : e[x])
{
if (t == fa)
continue;
// 向下递
DFS(t, x);
// 向上归
size[x] += size[t];
mx = max(mx, size[t]);
}
mx = max(mx, n - size[x]);
//找出最小值
if (ans > mx)
ans = mx, pos = x;
}
int main()
{
cin >> n;
for (int i = 1; i <= n - 1; i++)
{
cin >> a >> b;
e[a].push_back(b);
e[b].push_back(a);
}
DFS(1, 0);
cout << pos << " " << ans << endl;
for (int i = 1; i <= n; i++)
cout << size[i] << endl;
return 0;
}
5 树的直径
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5;
struct edge
{
int v, w;
};
vector<edge> e[N];
int n; // 结点数
int a, b, c;
int ans;
int dfs(int x, int fa)
{
int d1 = 0, d2 = 0; // 最大边和次大边
for (auto y : e[x])
{
int v = y.v, w = y.w;
if (v == fa)
continue;
int d = dfs(v, x) + w;
if (d >= d1)
d2 = d1, d1 = d;
else if (d > d2)
d2 = d;
}
ans = max(ans, d1 + d2);
return d1;
}
int main()
{
cin >> n;
for (int i = 1; i <= n - 1; i++)
{
cin >> a >> b >> c;
e[a].push_back({b, c});
e[b].push_back({a, c});
}
dfs(1, 0);
cout << ans << endl;
return 0;
}
6 树的中心
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
struct edge
{
int v, w;
};
vector<edge> e[N]; // 存储树
int d1[N], d2[N]; // 最长边和次长边权值
int up[N], path[N]; // path记录最长的路径
int a, b, c;
int n;
/**
* 测试用例
5
2 1 1
3 2 1
4 3 1
5 1 1
*/
void dfs_d(int x, int fa)
{
for (auto ex : e[x])
{
int y = ex.v, z = ex.w;
if (y == fa)
continue;
dfs_d(y, x);
if (d1[y] + z > d1[x])
d2[x] = d1[x], d1[x] = d1[y] + z, path[x] = y;
else if (d1[y] + z > d2[x])
d2[x] = d1[y] + z;
}
}
// 自上而下,由父节点更新子节点
void dfs_up(int x, int fa)
{
for (auto ex : e[x])
{
int y = ex.v, z = ex.w;
if (y == fa)
continue;
// 若在最长路径上
if (path[x] == y)
up[y] = max(up[x], d2[x]) + z;
else
up[y] = max(up[x], d1[x]) + z;
dfs_up(y, x);
}
}
int main()
{
cin >> n;
for (int i = 1; i < n; i++)
{
cin >> a >> b >> c;
e[a].push_back({b, c});
e[b].push_back({a, c});
}
dfs_d(1, 0);
dfs_up(1, 0);
// 求中心
int ans = 2e9;
for (int i = 1; i <= n; i++)
ans = min(ans, max(d1[i], up[i]));
cout << ans << endl;
return 0;
}
[Luogu P1596 USACO10OCT]Lake Counting S水坑计数
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int dx[] = {-1, -1, -1, 0, 0, 1, 1, 1};
int dy[] = {-1, 0, 1, -1, 1, -1, 0, 1};
int m, n;
char ma[N][N];
int ans; // 统计数
void dfs(int x, int y)
{
ma[x][y] = '.';
for (int i = 0; i < 8; i++)
{
int a = x + dx[i], b = y + dy[i];
if (a < 0 || a >= n || b < 0 || b >= m || ma[a][b] == '.')
continue;
dfs(a, b);
}
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i++)
cin >> ma[i];
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
if (ma[i][j] == 'W')
{
ans++;
dfs(i, j);
}
}
}
cout << ans << endl;
return 0;
}
4 广搜(BFS)算法
1.bfs模板
#include<bits/stdc++.h>
using namespace std;
const int N=1e7+10;
vector<int> e[N]; //保存图
queue<int> q;
int vis[N];
/**
* 将相邻结点入队
* 出队时,将出队元素的所有领点入队
* 为避免重复,设置vis数组记录是否被入队
**/
void bfs(int x)
{
q.push(x);
vis[x]=1;
while(q.size())
{
int s=q.front();
cout<<s<<endl;
q.pop();
for(auto t:e[s])
{
if(vis[t])
continue;
vis[t]=1;
q.push(t);
}
}
}
int main()
{
int n,a,b;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a>>b;
e[a].push_back(b);
e[b].push_back(a);
}
bfs(1);
}
迷宫问题
// #include <bits/stdc++.h>
#include <iostream>
#include <queue>
using namespace std;
const int N = 1010;
typedef pair<int, int> pa;
int maze[N][N];
int dx[] = {1, 0, 0, -1};
int dy[] = {0, 1, -1, 0};
int n;
struct node
{
int x, y;
} path[N][N]; // 记录路径
void bfs(int x, int y)
{
queue<node> q;
q.push({x, y});
maze[x][y] = 1;
while (q.size())
{
node u = q.front();
q.pop();
for (int i = 0; i < 4; i++)
{
int a = u.x + dx[i], b = u.y + dy[i];
if (a < 0 || a >= n || b < 0 || b >= n || maze[a][b])
continue;
maze[a][b] = 1;
path[a][b] = {u.x, u.y};
q.push({a, b});
}
}
}
int main()
{
cin >> n;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
cin >> maze[i][j];
}
bfs(n - 1, n - 1);
node p = {0, 0};
while (1)
{
if (p.x == n - 1 && p.y == n - 1)
break;
cout << "(" << p.x << ", " << p.y << ")" << endl;
p = path[p.x][p.y];
}
puts("(4, 4)");
return 0;
}
chapter 9 数学
1 高精度快速幂
luoguP1226 快速幂
#include <bits/stdc++.h>
using namespace std;
long long a, b, p;
long long quickpow(long long a, long long n, long long p)
{
long long res = 1;
// 对底数倍增,对指数进行二进制拆分
while (n)
{
if (n & 1)
res = res * a % p;
a = a * a % p;
n >>= 1;
}
return res;
}
int main()
{
cin >> a >> b >> p;
cout << a << "^" << b << " mod " << p << "=" << quickpow(a, b, p) << endl;
return 0;
}
[Luogu P1045 NOIP2003 普及组] 麦森数
#include <bits/stdc++.h>
using namespace std;
const int N = 500;
typedef vector<int> VI;
VI a(N), res(2 * N);
int p;
// 大整数乘法
VI mul_t(VI &a, VI &b)
{
VI t(N * 2);
for (int i = 0; i < N; i++)
{
for (int j = 0; j < N; j++)
{
t[i + j] += a[i] * b[j];
t[i + j + 1] += t[i + j] / 10; // 保存进位
t[i + j] %= 10; // 保存位的值
}
}
return t;
}
// 快速幂
void quickpow(int p)
{
res[0] = 1, a[0] = 2;
while (p)
{
if (p & 1)
res = mul_t(a, res);
a = mul_t(a, a);
p >>= 1;
}
res[0]--;
}
int main()
{
cin >> p;
cout << (int)(p * log10(2) + 1) << endl;
quickpow(p);
for (int i = 0, k = 499; i < 10; i++)
{
for (int j = 0; j < 50; j++, k--)
{
printf("%d", res[k]);
}
puts("");
}
return 0;
}
2 矩阵快速幂
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 110;
const int mod = 1e9 + 7;
int n;
LL k;
struct matrix
{
LL c[100][100];
// 进行初始化
matrix() { memset(c, 0, sizeof c); }
} A, res; // 这里的A保存原来的矩阵,res保存结果
// 重载矩阵的*号
matrix operator*(matrix &a, matrix &b)
{
matrix t; // 用于返回结果
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
for (int k = 1; k <= n; k++)
{
t.c[i][j] = (t.c[i][j] + a.c[i][k] * b.c[k][j]) % mod;
}
}
}
return t;
}
// 快速幂
void quickpow(LL k)
{
// 初始化t为单位矩阵
for (int i = 1; i <= n; i++)
res.c[i][i] = 1;
while (k)
{
if (k & 1)
res = res * A;
A = A * A;
k >>= 1;
}
}
int main()
{
cin >> n >> k;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
cin >> A.c[i][j];
}
}
// 利用快速幂进行计算
quickpow(k);
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
printf("%d ", res.c[i][j]);
}
puts("");
}
return 0;
}
3 矩阵加速递推
//解决斐波那契数列的方式很巧妙
斐波那契数列:f[n]=f[n-1]+f[n-2] n>2 当n<=2时,为1;
[f[n] f[n-1]]-->[f[n-1] f[n-2]]
f[n]=f[n-1]*1+f[n-2]*1
f[n-1]=f[n-1]*1+f[n-2]*0
可以推出=>[f[n] f[n-1]]=[f[n-1] f[n-2]]*{[1 1]*[1 0]}
#include <bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7;
typedef long long LL;
LL n;//要注意体重要求的n的值的范围
struct matrix
{
LL c[3][3];
// 矩阵初始化
matrix() { memset(c, 0, sizeof c); }
} A, res;
// 重载矩阵乘法
matrix operator*(matrix &a, matrix &b)
{
matrix t; // 用于返回结果
for (int i = 1; i <= 2; ++i)
for (int j = 1; j <= 2; ++j)
for (int k = 1; k <= 2; ++k)
t.c[i][j] = (t.c[i][j] + a.c[i][k] * b.c[k][j]) % mod;
return t;
}
// 进行快速幂运算
void quickpow(LL n)
{
// 初始化A矩阵
A.c[1][1] = A.c[1][2] = A.c[2][1] = 1;
// 初始化res矩阵
res.c[1][1] = res.c[1][2] = 1;
while (n)
{
if (n & 1)
res = res * A;
A = A * A;
n >>= 1;
}
}
int main()
{
cin >> n;
if (n <= 2)
{
puts("1");
return 0;
}
quickpow(n - 2);
cout << res.c[1][1] << endl;
return 0;
}
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod = 1e9 + 7;
LL n;
int t;
//
struct matrix
{
LL c[4][4];
matrix() { memset(c, 0, sizeof c); }
} A, ans;
// 重载乘法运算
matrix operator*(matrix &a, matrix &b)
{
matrix t;
for (int i = 1; i <= 3; i++)
for (int j = 1; j <= 3; j++)
for (int k = 1; k <= 3; k++)
t.c[i][j] = (t.c[i][j] + a.c[i][k] * b.c[k][j]) % mod;
return t;
}
// 快速幂
void quickpow(LL n)
{
// 初始化
ans.c[1][1] = ans.c[1][2] = ans.c[1][3] = 1;
memset(A.c, 0, sizeof A.c);
A.c[1][1] = A.c[3][1] = A.c[1][2] = A.c[2][3] = 1;
while (n)
{
if (n & 1)
ans = ans * A;
A = A * A;
n >>= 1;
}
}
int main()
{
cin >> t;
while (t--)
{
cin >> n;
if (n <= 3)
{
puts("1");
continue;
}
quickpow(n - 3);
cout << ans.c[1][1] << endl;
}
return 0;
}
4 进制转换
16进制转8进制
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int c[N];
/**
* cin、cout、printf 默认都是十进制式,如果需要输入或输出十六进制,
* 需要重新指定进制式。对于cin、cout ,其中 oct为八进制,hex为十六进制,dec为十进制。
* 特别注意,一旦指明进制数后,将一直有效,除非重新指定进制数。
*/
/**
* 将16进制的数先转换为2进制数,之后通过在高位补0,3位3位的取出值,转换为8进制的数
*/
int main()
{
int n;
cin >> n;
char ch;
while (n--)
{
string s, s2;
s2 = "";
cin >> s;
for (int i = 0; i < s.size(); i++)
{
switch (s[i])
{
case '0':
s2 += "0000";
break;
case '1':
s2 += "0001";
break;
case '2':
s2 += "0010";
break;
case '3':
s2 += "0011";
break;
case '4':
s2 += "0100";
break;
case '5':
s2 += "0101";
break;
case '6':
s2 += "0110";
break;
case '7':
s2 += "0111";
break;
case '8':
s2 += "1000";
break;
case '9':
s2 += "1001";
break;
case 'A':
s2 += "1010";
break;
case 'B':
s2 += "1011";
break;
case 'C':
s2 += "1100";
break;
case 'D':
s2 += "1101";
break;
case 'E':
s2 += "1110";
break;
case 'F':
s2 += "1111";
break;
default:
break;
}
}
int len = s2.size();
if (len % 3 == 1)
s2 = "00" + s2;
else if (len % 3 == 2)
s2 = "0" + s2;
int flag = 0;
for (int i = 0; i <= s2.size() - 3; i += 3)
{
int num = 4 * (s2[i] - '0') + 2 * (s2[i + 1] - '0') + (s2[i + 2] - '0');
if (num)
flag = 1;
if (flag)
printf("%d", num);
}
puts("");
}
return 0;
}
4 全排列问题
#include <bits/stdc++.h>
using namespace std;
int a[10];
int n;
int main()
{
cin >> n;
int j = 1; // 计算全排列的大小
for (int i = 1; i <= n; i++)
{
a[i] = i;
}
do
{
for (int j = 1; j <= n; j++)
printf("%5d", a[j]);
puts("");
/* code */
} while (next_permutation(a + 1, a + n + 1));//调用c++的库函数
return 0;
}
5 最大公约数-欧几里得算法
[Luogu P1029 NOIP2001 普及组] 最大公约数和最小公倍数问题
#include<iostream>
using namespace std;
#define int long long
int x,y;
int ans;
int gcd(int a,int b)
{
return b==0?a:gcd(b,a%b);
}
signed main()
{
cin>>x>>y;
int t=x*y;
for(int i=1;i*i<=t;i++)
{
if(t%i==0&&gcd(i,t/i)==x)
ans+=2;
}
if(x==y)
ans--;
cout<<ans<<endl;
return 0;
}
6 试除法判质数
Luogu P5736 【深基7.例2】质数筛
#include <iostream>
#include <math.h>
// #include<bits/stdc++.h> //万能头文件,切记有些比赛是禁用的
using namespace std;
bool is_prim(int a)
{
if (a == 1)
return 0;
for (int i = 2; i <= sqrt(a); i++) //为了避免i*i<=a,i*i过大,故这里采用根号
{
if (a % i == 0)
return 0;
}
return 1;
}
int main()
{
int n;
cin >> n;
int a;
for (int i = 0; i < n; i++)
{
cin >> a;
if (is_prim(a))
{
cout << a << " ";
}
}
cout << endl;
return 0;
}
7 分解质因数-唯一分解定理 试除法
Luogu P2043 质因子分解
-- 唯一分解定理:任何一个数都可以是 一些质数的次方的乘积
#include <iostream>
using namespace std;
const int N = 1e4 + 10;
int a[N]; // 质因子的个数
int n;
void decompose(int x) // 分解质因数
{
for (int i = 2; i * i <= x; i++)
{
while (x % i == 0)
{
a[i]++;
x /= i;
}
}
if (x > 1)
{
a[x]++;
}
}
int main()
{
cin >> n;
for (int i = 2; i <= n; i++)
decompose(i);
for (int i = 2; i <= n; i++)
{
if (a[i])
{
cout << i << " " << a[i] << endl;
}
}
return 0;
}
欧拉筛法,筛质数
Luogu P3383 【模板】线性筛素数
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1e8 + 10;
int n, q;
int vis[N]; // 划掉合数
int prim[N]; // 记录质数
int cnt; // 记录质数个数
// 线性筛法
void get_prim(int n)
{
for (int i = 2; i <= n; i++)
{
if (!vis[i])
prim[++cnt] = i;
for (int j = 1; 1LL * i * prim[j] <= n; j++)
{
vis[i * prim[j]] = 1;
if (i % prim[j] == 0)
break;
}
}
}
int main()
{
cin >> n >> q;
get_prim(n);
int t;
while (q--)
{
cin >> t;
cout << prim[t] << endl;
}
return 0;
}
chapter10 STL
1 set
P5250 木材仓库
chapter11 每日一题
2024/4/17
1 团队舞力最大值
思路:
本题主要难点为要解决超时问题,再尽可能短的时间内,运行出结果。可以从数组的第2个元素开始循环数组,直到倒数第2个,找出左右两边的最大值,在除以当前下标为i的数组值,并与当前的最大值做判断,更新最大值。
注意:为了避免在找最值时造成时间的浪费,在利用数组保存最值。
#include <iostream>
using namespace std;
#include <algorithm>
int main()
{
int N;
cin>>N;
int *W=new int[N];
int *maxl=new int [N];
int *maxr=new int [N];
if(N>=3)
{
for(int i=0;i<N;i++)
{
cin>>W[i];
}
maxl[0]=W[0];
maxr[N-1]=W[N-1];
for(int i=1;i<N;i++)
{
maxl[i]=max(maxl[i-1],W[i]);
}
for(int i=N-2;i>=0;i--)
{
maxr[i]=max(maxr[i+1],W[i]);
}
int maxW=0;
for(int i=1;i<N-2;i++)
{
int ml=maxl[i-1];
int mr=maxr[i+1];
int w=(ml+mr)/W[i];
if(maxW<w)
{
maxW=w;
}
}
cout<<maxW;
return 0;
}
else
{
return 0;
}
}
2 leetcode 392. 判断子序列
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"
是"abcde"
的一个子序列,而"aec"
不是)。
进阶:
如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
致谢:
特别感谢 @pbrother 添加此问题并且创建所有测试用例。
示例 1:
输入:s = "abc", t = "ahbgdc"
输出:true
示例 2:
输入:s = "axc", t = "ahbgdc"
输出:false
题解思路:在进行本题时,可以利用string中的find函数去解决,也可以使用双指针去遍历。核心时要子串中的所有字符,按递增顺序出现在父串中。利用官方题解中的双指针更好一些。
代码如下:
利用string中的find函数。
class Solution {
public:
bool isSubsequence(string s, string t) {
int size=s.size();
int cpos=0;
for(int i=0;i<size;i++)
{
int pos=t.find(s[i],cpos);
if(pos==-1)
{
return false;
}
else
{
cpos=pos+1;
}
}
return true;
}
};
//利用双指针
class Solution {
public:
bool isSubsequence(string s, string t) {
int s_size=s.size(),t_size=t.size();
int i=0,j=0;
while(i<s_size&&j<t_size)
{
if(s[i]==t[j])
{
i++;
}
j++;
}
return i==s;
}
};
2024/4/18
1 2007. 从双倍数组中还原原数组
//总结如下:
/*
实现方式:
1.排序+哈希表
2.排序+队列
3.消消乐
*/
class Solution {
public:
vector<int> findOriginalArray(vector<int>& changed) {
if(changed.size()%2!=0)
return {};
sort(changed.begin(),changed.end());
const int N=1e5+10;
vector<int> a;
int vis[N];//标注是否被访问
memset(vis,0,sizeof vis);
for(int l=0,r=1;l<changed.size();l++)
{
if(vis[l])
continue;
// for(int r=l+1;r<changed.size();r++)
// {
// if(changed[l]!=0)
// {
// if(changed[l]*2==changed[r]&&vis[r]==0)
// {
// a.push_back(changed[l]);
// //cout<<changed[l]<<endl;
// vis[l]=vis[r]=1;
// break;
// }
// }
// else
// {
// if(changed[r]==0&&vis[r]==0)
// {
// a.push_back(changed[l]);
// //cout<<changed[l]<<endl;
// vis[l]=vis[r]=1;
// break;
// }
// }
// }
while((l==r)||(r<changed.size()&&changed[l]*2!=changed[r]))
r++;
if(r==changed.size())
return {};
vis[r++]=1;
a.push_back(changed[l]);
}
// if(2*a.size()==changed.size())
// return a;
// else
// return {};
return a;
}
};
2024/4/21
洛谷Treasure G P6204 [USACO07CHN] Treasure G - 洛谷
//本题初步思想为最小生成树,然后判断有几层
2024/5/2
1 luoguP1090(见基础算法-贪心算法1)
chapter 12 真题
P8597 [蓝桥杯 2013 省 B] 翻硬币
P8681 [蓝桥杯 2019 省 AB] 完全二叉树的权值
P8707 [蓝桥杯 2020 省 AB1] 走方格
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 40;
ll ans; // 保存方案数
int dx[] = {1, 0};
int dy[] = {0, 1};
int n, m;
void dfs(int x, int y)
{
if (x == n && y == m)
{
ans++;
}
for (int i = 0; i < 2; i++)
{
int a = x + dx[i], b = y + dy[i];
if (a > n || b > m || (a % 2 == 0 && b % 2 == 0))
continue;
dfs(a, b);
}
}
int main()
{
cin >> n >> m;
if (n % 2 == 0 && m % 2 == 0)
ans = 0;
else
dfs(1, 1);
cout << ans << endl;
return 0;
}
/**
* 深搜会超时
* 1.记忆化搜索
* 2.动态规划
*
*/
// 1.记忆化搜索
#include <bits/stdc++.h>
using namespace std;
const int N = 40;
int f[N][N]; // 保存方案数
int dx[] = {1, 0};
int dy[] = {0, 1};
int n, m;
int dfs(int x, int y)
{
if (x & 1 || y & 1) // 满足条件
{
if (f[x][y]) // 是否被访问
return f[x][y];
for (int i = 0; i < 2; i++)
{
int a = x + dx[i], b = y + dy[i];
if ((a & 1 || b & 1) == 0 || a > n || b > m)
continue;
f[x][y] += dfs(a, b);
}
}
return f[x][y];
}
int main()
{
cin >> n >> m;
f[n][m] = n & 1 || m & 1;
if (!f[n][m])
puts("0");
else
cout << dfs(1, 1) << endl;
return 0;
}
//2.动态规划
/**
* 动态规划解决走方格问题
* 状态转移方程
* f(i,j)=f(i-1,j)+f(i,j-1)
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 40;
int n, m;
int f[N][N];
int main()
{
cin >> n >> m;
f[1][1] = 1;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
// 判断是否满足条件可以走
if ((i & 1 || j & 1) && (i != 1 || j != 1)) // i,j不同为偶数,且i,j不同时等于0
{
f[i][j] = f[i - 1][j] + f[i][j - 1];
}
}
}
cout << f[n][m] << endl;
return 0;
}
P8721 [蓝桥杯 2020 省 AB3] 填空问题(缺少 inc.txt, E 题数据)
P8776 [蓝桥杯 2022 省 A] 最长不下降子序列P8777 [蓝桥杯 2022 省 A] 扫描游戏
chapter 13 蓝桥杯国赛真题
细碎知识
1 文件读取
#include<iostream>
#include<fstream>
//注意string不能少
#include<string>
using namespace std;
int main()
{
ifstream infile;
infile.open("./numlist.txt",ios::in);
if(!infile.is_open())
{
return 0;
}
int ans=0;
char a[1024];
while(infile>>a)
{
unsigned long long b=stoll(a);//数字与字符串转换
if(!(b&1))
ans++;
}
cout<<ans<<endl;
return 0;
}