一.深度优先搜索的剪枝
1.可行性剪枝
下面的算法用于从0~30个数中选取8个,使其和为200.每一个数有选与不选两个枝,若选取得数字个数已经大于8个,将这个枝剪去,若数字和s已经大于200,则将这个枝剪去。
这种判断解是否可行得剪枝称位可行性剪枝。
#include
using namespace std;
int n, k, sum, ans;
int a[40];
void dfs(int i, int cnt, int s) {
if(cnt>k){
return;
}
if(s>sum){
return;
}
if (i == n) {
if (cnt == k && s == sum) {
ans++;
}
return;
}
dfs(i + 1, cnt, s);
dfs(i + 1, cnt + 1, s + a[i]);
}
int main() {
n = 30;
k = 8;
sum = 200;
for (int i = 0; i < 30; i++) {
a[i] = i + 1;
}
ans = 0;
dfs(0, 0, 0);
cout << ans << endl;
return 0;
}
2.最优性剪枝
这是寻找地图中起点到终点最短路径的算法
ans用于存储目前最小的步数,如果某一条路径长度(step)已经大于ans了,就将这条枝剪去,这样寻找最优解决办法的剪枝叫做最优性剪枝。若到达终点时步数小于ans,则更新ans的大小。
#include
#include
#include
using namespace std;
int n,m;
string maze[110];
bool vis[110][110];
int dir[4][2]={{-1,0},{0,-1},{1,0},{0,1}};
int ans=100000;
bool in(int x,int y){
return 0<=x&&x=ans){
return;
}
if(maze[x][y]=='T'){
ans=step;
return;
}
vis[x][y]=1;
for(int i=0;i<4;i++){
int tx=x+dir[i][0];
int ty=y+dir[i][1];
if(in(tx,ty)&&maze[tx][ty]!='*'&&!vis[tx][ty]){
dfs(tx,ty,step+1);
}
}
vis[x][y]=0;
}
int main() {
cin>>n>>m;
for(int i=0;i>maze[i];
}
int x,y;
for(int i=0;i
3.重复性剪枝
在与顺序无关的深搜中,我们最常用到重复性剪枝,例如从n个整数里选k个数使得和为s。这种与顺序无关的dfs中我们常用重复性剪枝。
#include
using namespace std;
int n, k, sum, ans;
int a[40];
bool xuan[40];
void dfs(int s, int cnt,int pos) {
if(s>sum||cnt>k){
return;
}
if (s == sum && cnt == k) {
ans++;
}
for (int i = pos; i < n; i++) {
if (!xuan[i]) {
xuan[i] = 1;
dfs(s + a[i], cnt + 1,i+1);
xuan[i] = 0;
}
}
}
int main() {
n = 30;
k = 8;
sum = 200;
for (int i = 0; i < 30; i++) {
a[i] = i + 1;
}
ans = 0;
dfs(0, 0,0);
cout << ans << endl;
return 0;
}
4.奇偶性剪枝
本质上来说是一种可行性剪枝
从起点到终点,要求正好T步可以到达
设某一点得横坐标与纵坐标之和为奇书就是白色,偶数就是黑色。易得如果起点与终点同色,则必须走偶数步,不同色则必须走奇数步。可以根据这个奇偶性对结果进行剪枝。
#include
using namespace std;
const int N = 10;
int n, m, T;
char mat[N][N];
bool vis[N][N];
int dx[4]={0,0,-1,1};
int dy[4]={1,-1,0,0};
bool ok;
void dfs(int x,int y,int t){
if(ok)return;
if(t==T){
if(mat[x][y]=='D')ok=true;
return;
}
vis[x][y]=true;
for(int i=0;i<4;i++){
int tx=x+dx[i];
int ty=y+dy[i];
if(tx<0||tx>=n||ty<0||ty>=m||mat[tx][ty]=='X'||vis[tx][ty])
continue;
dfs(tx,ty,t+1);
}
vis[x][y]=false;
}
int main() {
cin >> n >> m >> T;
for (int i = 0; i < n; ++i) {
cin >> mat[i];
}
int sx,sy,ex,ey;
for(int i=0;i
dfs剪枝习题
1.给一个数n,要求你找出一个只由0和1组成的得十进制数m,使这个正整数m可以被n整除。
#include
#include
using namespace std;
int n;
long long ans;
bool flag=false;
void dfs(long long num,int depth){
if(flag||depth>18)return;//最优性剪枝
if(num%n==0){
ans=num;
flag=true;
return;
}
dfs(num*10,depth+1);
dfs(num*10+1,depth+1);
}
int main(){
cin>>n;
dfs(1,1);
cout<
2.全排列
给出一个数n,输出n的全排列的排列个数,并按字母序输出
#include
#include
#include
#include
#define numToChar(x) (x+'0')
using namespace std;
int n;
bool vis[10];
vector save;
void dfs(int num,string ans,int step){
ans+=numToChar(num);
if(step==n){
save.push_back(ans);
return;
}
for(int i=1;i<=n;++i){
if(!vis[i]){
vis[i]=true;
dfs(i,ans,step+1);
vis[i]=false;
}
}
}
int main(){
memset(vis,0,sizeof(vis));
cin>>n;
for(int i=1;i<=n;++i){
vis[i]=true;
dfs(i,"",1);
vis[i]=false;
}
cout<
3.蒜头君的旅游计划
#include
#include
using namespace std;
int n,ans=150010;
int mat[16][16];
bool vis[16];
void dfs(int city,int num,int money){
if(num==n){
ans=min(ans,money+mat[city][0]);
return;
}
if(money>=ans)return;//最优性剪枝
for(int i=1;i>n;
for(int i=0;i>mat[i][j];
}
}
dfs(0,1,0);
cout<
4.正方形
#include
#include
using namespace std;
int sum=0;
int n,mat[30];
bool vis[30];
bool flag=false;
void dfs(int index,int cnt,int len){
if(flag||len>(sum/4))return;
if(cnt==3){
flag=true;
return;
}
if(len==sum/4){
len=0;
cnt++;
index=0;
}
for(int i=index;i>n;
for(int i=0;i>mat[i];
sum+=mat[i];
}//搜索出四个长度相同的木棒
bool f1=true;
for(int i=0;isum/4)f1=false;
}
if(sum%4==0&&f1)dfs(0,0,0);
flag?cout<<"Yes":cout<<"No";
return 0;
}
二,图
当边的个数时,我们称其为稀疏图,反之为稠密图。无向图中每一对顶点间相连叫完全图,在有向图中任何一对顶点间都有两条边,则称为有向完全图。
度的概念
在无向图中,顶点的度指某个顶点连出的边数。
把图中所有顶点的排列成一个序列s,s称位一个度序列。图中的边数为总度数的一半。
在有向图中,和度对应的有入度和出度两个概念,入度指以该顶点为终点的有向边数量,出度指以该顶点为起点的有向边数量。且总的入度数=总的出度数
一个度序列是否可图,可以运用Havel-Hakimi定理:
s:d1,d2,d3,d4,d5....dn可图,那么d2-1,d3-1,d4-1,d5-1,....d(d1)-1.....dn可图....
证明:4 3 2 2 1可图
--->2 1 1 0
--->0 0 0 所以4 3 2 2 1可图
核心代码:
bool Havel_Hakimi(){
for(int i=0; i());
if(i+arr[i] >= n) return false;
for(int j=i+1; j<=i+arr[i] ; ++j){
--arr[j];
if(arr[j] < 0) return false;
}
}
if(arr[n-1]!=0) return false;
return true;
}
图的存储方法
1.邻接矩阵存储图的例题:找朋友
G[i][j]=1代表i到j存在一条有向边
#include
#include
using namespace std;
int main() {
int G[6][6];
memset(G,0,sizeof(G));
int m;
cin>>m;
for(int i=0;i>a>>b;
G[a][b]=1;
}
for(int i=1;i<=5;i++){
int sum=0;
for(int j=1;j<=5;j++){
if(G[i][j]==1&&G[j][i]==1){
sum++;
}
}
cout<
2.邻接表
对于每一个顶点a,都用一个vector存储a到b的有向边
具体操作:G[a].push_back(b)
邻接表的好处是存储稀疏图时比较省空间,但是查询起来效率较低。
在稀疏图中我们常用邻接表,稠密图我们常用邻接矩阵。
3.带权值的图的存储方法
1)邻接矩阵 G[a][b]存储a到b的边权
2)邻接表 用一个struct存储
#include
#include
using namespace std;
struct node{
int v,w;
};
vector G[11];
void insert1(int u,int v,int w){
node temp;
temp.v=v;
temp.w=w;
G[u].push_back(temp);
}
void insert2(int u,int v,int w){
insert1(u,v,w);
insert1(v,u,w);
}
void input(){
int m;
cin>>m;
for(int i=0;i>u>>v>>w;
insert2(u,v,w);
}
}
int main() {
return 0;
}
基于链表的邻接表:
const int M = 1000000;
const int N = 10000;
struct edge {
int v, d, next;
} e[M];
int p[N], eid;
void init() { // 初始化,在建图之前必须进行
memset(p, -1, sizeof(p));
eid = 0;
}
void insert(int u, int v, int d) { // 插入单向边
e[eid].v = v;
e[eid].d = d;
e[eid].next = p[u];
p[u] = eid++;
}
void insert2(int u, int v, int d) { // 插入双向边
insert(u, v, d);
insert(v, u, d);
}
void output(int n) { // 输出整张图中的所有边
for (int i = 0; i < n; i++) {
for (int j = p[i]; j != -1; j = e[j].next) { // 遍历从 i 连出的所有边
cout << i << "->" << e[j].v << ", " << e[j].d << endl;
}
}
}
关系查询:
#include
#include
蒜头君的旅行计划
#include
#include
#include
using namespace std;
int n,m;
struct node{
int pos,w;
};
bool vis[50000];
vector> e;
void play(int pos){
if(vis[pos]){
return;
}
vis[pos]=true;
printf("%d ",pos);
int m=1000010,index=-1;
for(int i=0;i>n>>m;
e.resize(n+1);
for(int i=0;i
图习题
1.完全图的判定
#include
using namespace std;
int n,m;
int main(){
cin>>n>>m;
bool e[110][110];
int cnt=0;
for(int i=0;i>u>>v;
if(!e[u][v]){
e[u][v]=e[v][u]=true;
cnt++;
}
}
if(cnt==n*(n-1)/2)cout<<"Yes"<
三、最短路
由于dfs解决最短路问题非常麻烦,而bfs只能解决权值为1的最短路,所以我们引入了最短路算法。
1.Dijkstra
Dijkstra用于解决单源最短路问题,特点是以起点为中心,逐层向外扩展,值得注意的是,这个算法不能处理负边。(bfs+贪心)
算法步骤
首先初始化一个dist[v]集合,它代表起点s到顶点v的距离,将起点s的dist初始化为0,其他顶点置为inf
1.找出一个离源点最近的v,将v加入最短路集合U
2.用dist[v]和v连出来的边更新不在集合U的顶点,这一步称为松弛操作
3.不停重复1,2,直到V=U或者找不到新的点
如果V!=U,则说明找不到最短路径,否则dist[i]代表s到i的最短路径
算法演示:
#include
#include
using namespace std;
const int N = 1e3 + 9;
const int M = 1e4 + 9;
const int inf = 0x3f3f3f3f;
struct edge {
int v, w, fail;
edge() {}
edge(int _v, int _w, int _fail) {
v = _v;
w = _w;
fail = _fail;
}
} e[M << 1];
int head[N], len;
void init() {
memset(head, -1, sizeof(head));
len = 0;
}
void add(int u, int v, int w) {
e[len] = edge(v, w, head[u]);
head[u] = len++;
}
void add2(int u, int v, int w) {
add(u, v, w);
add(v, u, w);
}
int n, m;
int dis[N];
bool vis[N];
void dijkstra(int u) {
memset(vis, false, sizeof(vis));
memset(dis, 0x3f, sizeof(dis));
dis[u] = 0;//初始化
for (int i = 0; i < n; ++i) {
int mi = inf;
for (int j = 1; j <= n; ++j) {
if (!vis[j] && dis[j] < mi) {
mi = dis[u = j];//寻找最近的未加入集合U的顶点
}
}
if (mi == inf) {
return;
}
vis[u] = true;
for (int j = head[u]; ~j; j = e[j].fail) {//若找到,对所有与这个点相连并且未访问过的顶点进行松弛
int v = e[j].v;
int w = e[j].w;
if (!vis[v] && dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
}
}
}
}
int main() {
init();
int u, v, w;
cin >> n >> m;
while (m--) {
cin >> u >> v >> w;
add2(u, v, w);
}
dijkstra(1);
cout << dis[n] << endl;
return 0;
}
Dijkstra算法的核心思想是从未确定最短路的点的集合中选区一个最小的来更新其他的点,暴力枚举的话时间复杂度是,如果考虑堆优化,用一个set来维护点的集合,复杂度可以降到
set的第二个参数代表堆的排序方法
typedef pair PII;
set > min_heap;
int dist[MAX_N]; // 存储单源最短路的结果
bool vst[MAX_N]; // 标记每个顶点是否在集合 U 中
bool dijkstra(int s) {
// 初始化 dist、小根堆和集合 U
memset(vst, 0, sizeof(vst));
memset(dist, 0x3f, sizeof(dist));
min_heap.insert(make_pair(0, s));
dist[s] = 0;
for (int i = 0; i < n; ++i) {
if (min_heap.size() == 0) { // 如果小根堆中没有可用顶点,说明有顶点无法从源点到达,算法结束
return false;
}
// 获取堆顶元素,并将堆顶元素从堆中删除
set >::iterator iter = min_heap.begin();
int v = iter->second;
min_heap.erase(*iter);
vst[v] = true;
// 进行和普通 dijkstra 算法类似的松弛操作
for (int j = p[v]; j != -1; j = e[j].next) {
int x = e[j].v;
if (!vst[x] && dist[v] + e[j].w < dist[x]) {
// 先将对应的 pair 从堆中删除,再将更新后的 pair 插入堆
min_heap.erase(make_pair(dist[x], x));
dist[x] = dist[v] + e[j].w;
min_heap.insert(make_pair(dist[x], x));
}
}
}
return true; // 存储单源最短路的结果
}
习题
1.骑车比赛
#include
#include
#include
using namespace std;
const int inf=0x3f3f3f;
int n,m;
int e[1001][1001];
int dis[1001];
bool vis[1001],have_e[1001][1001];
void dijkstra(int u){
memset(dis,0x3f,sizeof(dis));
memset(vis,false,sizeof(vis));
dis[u]=0;
for(int i=0;idis[mint]+e[mint][j]){
dis[j]=dis[mint]+e[mint][j];
}
}
}
}
int main(){
cin>>n>>m;
int u,v,w;
while(m--){
cin>>u>>v>>w;
e[u][v]=e[v][u]=w;
have_e[u][v]=have_e[v][u]=true;
}
dijkstra(1);
cout<
SPFA单源最短路
SPFA单源最短路是bfs的队列优化版本
一般用di代表顶点到i的最短路,额外用一个队列来保存即将进行拓展的顶点列表用inqi来表示
算法步骤:
1.首先初始化,初始队列只包含源点,且ds=0
2.取出队列的头顶点u,扫描从顶点u出发的每条边,并进行松弛操作,若被松弛的顶点v不在队列中,将v入队
3.重复步骤2直到顶点为空
代码:
bool inq[MAX_N];
int d[MAX_N]; // 如果到顶点 i 的距离是 0x3f3f3f3f,则说明不存在源点到 i 的最短路
void spfa(int s) {
memset(inq, 0, sizeof(inq));
memset(d, 0x3f, sizeof(d));
d[s] = 0;
inq[s] = true;
queue q;
q.push(s);
while (!q.empty()) {
int u = q.front();
q.pop();
inq[u] = false;
for (int i = p[u]; i != -1; i = e[i].next) {
int v = e[i].v;
if (d[u] + e[i].w < d[v]) {
d[v] = d[u] + e[i].w;
if (!inq[v]) {
q.push(v);
inq[v] = true;
}
}
}
}
}
完整代码:
#include
#include
#include
using namespace std;
const int N = 1e3 + 9;
const int M = 1e4 + 9;
struct edge{
int v,w,fail;
edge(){}
edge(int _v,int _w,int _fail){
v=_v;
w=_w;
fail=_fail;
}
}e[M<<1];
int head[N],len;
void init(){
memset(head,-1,sizeof(head));
len=0;
}
void add(int u,int v,int w){
e[len]=edge(v,w,head[u]);
head[u]=len++;
}
void add2(int u,int v,int w){
add(u,v,w);
add(v,u,w);
}
int n, m;
int dis[N];
bool vis[N];
void spfa(int u){
memset(vis,false,sizeof(vis));
vis[u]=true;
memset(dis,0x3f,sizeof(dis));
dis[u]=0;
queue q;
q.push(u);
while(!q.empty()){
u=q.front();
q.pop();
vis[u]=false;
for(int j=head[u];~j;j=e[j].fail){
int v=e[j].v;
int w=e[j].w;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
if(!vis[v]){
q.push(v);
vis[v]=true;
}
}
}
}
}
int main() {
init();
int u,v,w;
cin>>n>>m;
while(m--){
cin>>u>>v>>w;
add2(u,v,w);
}
spfa(1);
cout<
SPFA判断负环
在进行spfa时,用一个cnt数组记录每个顶点的入队次数,如果一个顶点入队次数大于顶点总数n,则可以判断存在负环。
#include
#include
#include
using namespace std;
const int N = 1e3 + 9;
const int M = 1e4 + 9;
struct edge {
int v, w, fail;
edge() {}
edge(int _v, int _w, int _fail) {
v = _v;
w = _w;
fail = _fail;
}
} e[M << 1];
int head[N], len;
void init() {
memset(head, -1, sizeof(head));
len = 0;
}
void add(int u, int v, int w) {
e[len] = edge(v, w, head[u]);
head[u] = len++;
}
void add2(int u, int v, int w) {
add(u, v, w);
add(v, u, w);
}
int n, m;
int dis[N],in[N];
bool vis[N];
bool spfa(int u){
memset(vis,false,sizeof(vis));
vis[u]=true;
memset(dis,0x3f,sizeof(dis));
dis[u]=0;
memset(in,0,sizeof in);
in[u]=1;
queue q;
q.push(u);
while(!q.empty()){
u=q.front();
q.pop();
vis[u]=false;
for(int j=head[u];~j;j=e[j].fail){
int v=e[j].v;
int w=e[j].w;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
if(!vis[v]){
q.push(v);
vis[v]=true;
++in[v];
if(in[v]>n){
return true;
}
}
}
}
}
return false;
}
int main() {
init();
int u, v, w;
cin >> n >> m;
while (m--) {
cin >> u >> v >> w;
add2(u, v, w);
}
if(spfa(1)){
cout<<"yes"<
Floyd多源最短路算法
是一种基于动态规划的最短路算法
设是经过1~k点的i到j的最短路,对于k点,其可能经过k点或者不经过k点。
状态转移方程:
代码:
int g[N][N]; // 邻接矩阵存图
int dp[N][N][N];
void floyd(int n) {
for (int k = 0; k <= n; ++k) {
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if (k == 0) {
dp[k][i][j] = g[i][j];
} else {
dp[k][i][j] = min(dp[k - 1][i][j], dp[k - 1][i][k] + dp[k - 1][k][j]);
}
}
}
}
}
但还可以把k这一维优化掉,使dp数组退化为g数组,代码:
int g[N][N];
void floyd(int n) {
for (int k = 1; k <= n; ++k) {
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
}
}
}
}
完整代码:
#include
#include
using namespace std;
const int N = 101;
int g[N][N];
void floyd(int n){
for(int k=1;k<=n;++k){
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
}
}
}
}
int main() {
memset(g,0x3f,sizeof(g));
for(int i=0;i>n>>m;
while(m--){
cin>>u>>v>>w;
g[u][v]=g[v][u]=w;
}
floyd(n);
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
cout<
次短路解法
次短路也分为两种:1.可以经过同一个点 2.不可经过同一个点
第一种用dijkstra时,用两个dis数组,一个记录最短路,一个记录次短路,每次更新时,判断是否更新最短路与次短路的值。
第二种则枚举最短路的每条边,将其去掉,记录剩下的最短路,取其最小的一个就是答案。
#include
#include
#include
#include
#include
using namespace std;
int n,m;
struct node{
int x,y;
}nodes[300];
struct edge{
int v,fail;
double w;
edge(){}
edge(int _v,double _w,int _fail){
v=_v;
w=_w;
fail=_fail;
}
}e[50000];
int head[50000],len;
void init(){
memset(head,-1,sizeof(head));
len=0;
}
void insert(int u,int v,double w){
e[len]=edge(v,w,head[u]);
head[u]=len++;
}
void insert2(int u,int v,double w){
insert(u,v,w);
insert(v,u,w);
}
double dis1[300],dis2[300];
bool vis1[300],vis2[300];
void dijkstra(int u){
fill(dis1,dis1+n+1,1000100);
fill(dis2,dis2+n+1,1000100);
memset(vis1,false,sizeof(vis1));
memset(vis2,false,sizeof(vis2));
dis1[u]=0;
for(int i=1;i<=2*n;++i){
double mint=1000000;
int v=-1,k;
for(int j=1;j<=n;++j){
if(!vis1[j]&&dis1[j]mint+w){
dis2[to]=dis1[to];
dis1[to]=mint+w;
}else if(dis2[to]>mint+w)dis2[to]=mint+w;
}
}
}
int main(){
init();
cin>>n>>m;
int x,y;
for(int i=1;i<=n;++i){
cin>>x>>y;
nodes[i]={x,y};
}
int u,v;
for(int i=1;i<=m;++i){
cin>>u>>v;
double d=sqrt(pow(nodes[u].x-nodes[v].x,2)+pow(nodes[u].y-nodes[v].y,2));
insert2(u,v,d);
}
dijkstra(1);
printf("%.2lf\n",dis2[n]);
return 0;
}
4.线段树
一类问题可以抽象成n个数a1....an
1.查询[l,r]中最小的数
2.修改ai为x
解决这类问题,引入一个高级数据结构:线段树
用一棵二叉树来表示线段树,每个节点代表一个区间,每个非叶子结点都有左右两个子树,对每个结点,左结点编号2i,右结点编号2i+1,对于每一个结点如果其表示区间[l,r]。如果l=r,那么是一个叶子结点,否则令mid=(l+r)/2,左儿子为[l,mid],右儿子为[mid+1,r]
线段树的构建是个递归的过程,父节点的信息需要子节点去更新,所以需要递归的建立左右子树,代码如下:
const int maxn = 10010;
int minv[4 * maxn], a[maxn];
// id 表示结点编号,l, r 表示左右区间
void build(int id, int l, int r) {
if (l == r) {
minv[id] = a[l];
return;
}
int mid = (l + r) >> 1;
build(id << 1, l, mid);
build(id << 1 | 1, mid + 1, r);
minv[id] = min(minv[id << 1], minv[id << 1 | 1]);
}
建树的复杂度为O(n),由于结点编号不一定连续,所以minv数组大小一定要是节点数的四倍,这个经过了严格的数学推导。id << 1 | 1代表2*id+1,但是一定不能用+而要用|,因为+的优先级高于位运算。递归的建立完左右子树之后,我们需要用左右子树的信息更新当前节点,这一步一般称位pushup。
完整代码:
#include
using namespace std;
const int maxn = 110;
int a[maxn];
int minv[4*maxn];
void pushup(int id){
minv[id]=min(minv[id<<1],minv[id<<1|1]);
}
void build(int id,int l,int r){
if(l==r){
minv[id]=a[l];
return;
}
int mid=(l+r)>>1;
build(id<<1,l,mid);
build(id<<1|1,mid+1,r);
pushup(id);
}
int main() {
int n;
cin>>n;
for(int i=1;i<=n;++i){
cin>>a[i];
}
build(1,1,n);
return 0;
}
实现线段树的单点更新
更新一次的复杂度为,因为数的深度为
// 把 x 位置更为成为 v
void update(int id, int l, int r, int x, int v) {
if (l == r) {
minv[id] = v;
return;
}
int mid = (l + r) >> 1;
if (x <= mid) {
update(id << 1, l, mid, x, v);
} else {
update(id << 1 | 1, mid + 1, r, x, v);
}
pushup(id);
}
实现线段树的单点查询
单点查询和单点更新很像,沿着链走到叶子结点就行了。
int query(int id, int l, int r, int x) {
if (l == r) {
return minv[id];
}
int mid = (l + r) >> 1;
if (mid <= x) {
return query(id << 1, l, mid, x);
} else {
return query(id << 1 | 1, mid + 1, r, x);
}
}
区间查询
对于查询区间[x,y]就是将结点的区间合并起来,每个子区间完全被查询区间包含,查询区间的复杂度也是
int query(int id, int l, int r, int x, int y) {
if (x <= l && r <= y) { // 如果完全包含,直接返回
return minv[id];
}
int mid = (l + r) >> 1;
int ans = inf;
if (x <= mid) {
ans = min(ans, query(id << 1, l, mid, x, y)); // 如果左区间包含,递归的查询左子树
}
if (y > mid) {
ans = min(ans, query(id << 1 | 1, mid + 1, r, x, y)); // 如果右区间包含,递归的查询右子树
}
return ans;
}
完整代码:
#include
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 110;
int a[maxn];
int minv[4 * maxn];
void pushup(int id) {
minv[id] = min(minv[id << 1], minv[id << 1 | 1]);
}
void build(int id, int l, int r) {
if (l == r) {
minv[id] = a[l];
return;
}
int mid = (l + r) >> 1;
build(id << 1, l, mid);
build(id << 1 | 1, mid + 1, r);
pushup(id);
}
void update(int id, int l, int r, int x, int v) {
if (l == r) {
minv[id] = v;
return;
}
int mid = (l + r) >> 1;
if (x <= mid) {
update(id << 1, l, mid, x, v);
} else {
update(id << 1 | 1, mid + 1, r, x, v);
}
pushup(id);
}
int query(int id,int l,int r,int x,int y){
if(x<=l&&r<=y){
return minv[id];
}
int mid=(l+r)>>1;
int ans=inf;
if(x<=mid){
ans=min(ans,query(id<<1,l,mid,x,y));
}
if(y>mid){
ans=min(ans,query(id<<1|1,mid+1,r,x,y));
}
return ans;
}
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
build(1, 1, n);
int q;
cin >> q;
for (int i = 0; i < q; ++i) {
int x, v;
cin >> x >> v;
update(1, 1, n, x, v);
}
int p;
cin>>p;
for(int i=0;i>l>>r;
cout<
斑点蛇
用于记录区间和
#include
#include
using namespace std;
int N,a[50010],minv[200010];
void bulid(int id,int l,int r){
if(l==r){
minv[id]=a[l];
return;
}
int mid=(l+r)>>1;
bulid(id<<1,l,mid);
bulid(id<<1|1,mid+1,r);
minv[id]=minv[id<<1]+minv[id<<1|1];
}
void add(int id,int l,int r,int x,int v){
if(l==r){
minv[id]+=v;
return;
}
int mid=(l+r)>>1;
if(mid>=x)add(id<<1,l,mid,x,v);
else add(id<<1|1,mid+1,r,x,v);
minv[id]=minv[id<<1]+minv[id<<1|1];
}
void sub(int id,int l,int r,int x,int v){
if(l==r){
minv[id]-=v;
return;
}
int mid=(l+r)>>1;
if(x<=mid)sub(id<<1,l,mid,x,v);
else sub(id<<1|1,mid+1,r,x,v);
minv[id]=minv[id<<1]+minv[id<<1|1];
}
int query(int id,int l,int r,int x,int y){
if(x<=l&&y>=r){
return minv[id];
}
int mid=(l+r)>>1;
int ans=0;
if(x<=mid)ans+=query(id<<1,l,mid,x,y);
if(y>mid)ans+=query(id<<1|1,mid+1,r,x,y);
return ans;
}
int main(int argc, char** argv) {
cin>>N;
for(int i=1;i<=N;++i){
cin>>a[i];
}
bulid(1,1,N);
string input="";
int a,b;
while(1){
cin>>input;
if(input=="End")break;
cin>>a>>b;
if(input=="Add"){
add(1,1,N,a,b);
}else if(input=="Sub"){
sub(1,1,N,a,b);
}else if(input=="Query"){
cout<
5.拓扑排序
拓扑排序本质上是一种bfs,代码如下:
#include
#include
#include
using namespace std;
const int MAX_N = 100;
const int MAX_M = 10000;
struct edge {
int v, next;
int len;
} E[MAX_M];
int p[MAX_N], eid;
void init() {
memset(p, -1, sizeof(p));
eid = 0;
}
void insert(int u, int v) {
E[eid].v = v;
E[eid].next = p[u];
p[u] = eid++;
}
int n,m;
int indegree[MAX_N];
void topo(){
queue q;
for(int i=1;i<=n;i++){
if(indegree[i]==0){
q.push(i);
}
}
while(!q.empty()){
int now=q.front();
cout<>n>>m;
for(int i=0;i>u>>v;
insert(u,v);
indegree[v]++;
}
topo();
return 0;
}
欧拉回路
若图G中存在一条路径使其恰好通过G中的每条边一次,我们称之为欧拉路径(半欧拉图)。若该路是一个环路,我们称之为欧拉环路(欧拉图)。
判断是否是欧拉图,是否是欧拉回路
欧拉路径的性质,有且仅有两个点的入度为奇数
欧拉图的性质,所有点的入度都为偶数
#include
#include
using namespace std;
const int MAX_N = 100;
const int MAX_M = 10000;
struct edge {
int v, next;
int len;
} E[MAX_M];
int p[MAX_N], eid;
void init() {
memset(p, -1, sizeof(p));
eid = 0;
}
void insert(int u, int v) {
E[eid].v = v;
E[eid].next = p[u];
p[u] = eid++;
}
int n,m;
int degree[MAX_N];
int cnt;
bool vis[MAX_N];
void dfs(int u){
vis[u]=true;
cnt++;
for(int i=p[u];i!=-1;i=E[i].next){
int v=E[i].v;
if(!vis[v]){
dfs(v);
}
}
}
void euler(){
dfs(1);
if(cnt!=n){
cout<<"It doesn't have an euler path!"<>n>>m;
for(int i=0;i>u>>v;
insert(u,v);
insert(v,u);
degree[u]++;
degree[v]++;
}
euler();
return 0;
}