【概念理论】不存在的NOIP2016

时间:2017.6.17

0x01搜索

一、定义

1.从数据集合中找出目标元素进行处理。
2.当我们难以通过分析解决给定问题,或者找不到一个能有效解决问题的算法时,就不得不依靠反复的试错来寻求问题的答案了。  而且在这里,可以用启发式搜索,即利用问题拥有的启发信息来引导搜索,达到减少搜索范围、降低问题复杂度的目的。

二、对象

1.目的: 可以是需要进行处理的目标元素, 或者是答案本身。
2.类型: 可以是一个数值, 也可以是一个组合, 甚至可能可以是一个集合

三、数值

类型: 线性, 二分, 散列 
1.线性:从数组开头依次访问各元素,检查该元素是否与目标值相等。 线性搜索算法效率很低, 但是适用于任何形式的数据。
2.二分:通过不断不断缩小解存在的范围, 从而求得问题最优解的方法。 效率较高,应用较广。
3.散列:根据各元素的值来确定存储位置, 然后将位置保管在散列表中。 散列可以高效的执行动态插入,搜索,删除操作。

四、组合

1.穷竭搜索:将所有的可能性罗列出来,在其中寻找答案。
2.迭代加深:对于可以用回溯求解但解答树的深度没有明显上限的题目。
3.状态空间搜索:找到一个从初始状态到终止状态的路径。
4.回溯法:将生成与检查有机的结合起来,实现剪枝。 
5.A* IDA*:设计一个乐观的估价函数,实现剪枝。

五、练习题 2017.6.5

尚未能包含大框架,
详细见具体知识点。

0x02数据结构

一、基础

抽象数据类型(ADT):表(list,hash), 栈(stack), 队列(queue)
树相关(Tree):二叉树, 堆(heap)
库(STL):排序(sort), 集合(set), 映射(map)

二、中级

树相关: 二叉搜索树, 线段树, 
图相关: 并查集, 有向无环图(DAG), 

0x03图论

图的基本概念:

一、图是什么

1.定义:图由顶点和边构成。顶点代表对象,边代表两个对象之间的连接关系。记为图G=(V(点集),E(边集));
2.应用:图被应用于解决错综的网状关系的问题当中。 往往可以用之建立相关的模型套入相关算法以解决问题。

二、图的分类

1.依据一:边没有指向性的叫做无向图(朋友关系,路线图),边具有指向性的叫做有向图(数值大小,流程图)。
2.依据二:根据边有无附加的属性(具有权值),将图分为带权图,和不带权图。
3.依据三:根据图是否有环(从一个顶点出发经过若干边可以回到该顶点)分为有环图和无欢图。
4.依据四:任意两点之间都有路径的图叫做连通图,没有的叫非连通图。
5.典型的无向图:没有圈的连通图叫做树,没有圈的非连通图叫做森林。
6.典型的有向图:没有圈的有向图叫做DAG。

三、图的表示

0.背景:图作为与线性不同的另一种新的数据结构类型,为了能够在程序中对之进行处理,需要将之按某种方法存储下来。
1.邻接矩阵:用一个二维数组表示(int e[maxn][maxn];),e[i][j]表示顶点i与顶点j的距离,若不连通可以将之标记为inf。
2.邻接表一:用链表存储从顶点0出发有到顶点1,2,3的边(int first[maxn],next[maxn];),其中first[i]表示顶点i出发的第一条边,next[i]表示边i的下一条边(可以用-1表示不存在)。
3.邻接表二:用向量存储从某顶点出发有到其余顶点的边(vector<int>G[maxn];),其中G[i]存储了顶点i出发能够到达的其余边。

图的相关算法(本节为大纲,具体参见详细算法整理):

四、图的搜索

五、图的最短路

五、最小生成树

六、网络流

图相关的数据结构(本节已全部移至数据结构章节)
七、树

二叉树
最小堆
最大堆
并查集
线段树
AVL树
平衡树
红黑树
树套树
主席树
树状数组
二叉搜索树

八、图

0x04DFS

一、定义

深度优先搜索算法(英语:Depth-First-Search,简称DFS)是一种用于遍历或搜索树或图的算法。沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所在边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。属于盲目搜索。
深度优先搜索是图论中的经典算法,利用深度优先搜索算法可以产生目标图的相应拓扑排序表,利用拓扑排序表可以方便的解决很多相关的图论问题,如最大路径问题等等。

二、基本框架(待修改)

1.访问顶点v
2.依次从v的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和v有路径相通的顶点都被访问
3.若此时图中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到图中所有顶点均被访问过为止

三、用处

1.DFS求通块(因为能够遍历图, )
2.

四、本章节相关专题建议阅读顺序

1.求通块
2.全排列
3.回溯法
4.

练习题 2017.5.6

1.Codevs1294 全排列    
2.Codevs1295 N皇后问题    
3.Codevs3895 环素数       
4.Codevs1983 等式问题
5.Codevs1018 单词接龙
6.Codevs1026 逃跑的拉尔夫 

0x05最小生成树

一、讲道理

二、Kruscal

codevs1078

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int n, a[110][110], fa[110], co, ans;
struct side{
    int u, v, w; 
    side (int u, int v, int w):u(u),v(v),w(w){}
    bool operator < (const side b)const{ return w<b.w;}
};
vector<side>e;
int find(int x){ return x==fa[x]? x : fa[x]=find(fa[x]);}
int main(){
    cin>>n;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++){
            int x; cin>>x;
            if(i >= j)continue;
            e.push_back(side(i, j, x));
        }
    }
    sort(e.begin(), e.end());
    for(int i = 1; i <= n; i++)fa[i] = i;
    for(int i = 0; i < e.size(); i++){
        if(find(e[i].u) != find(e[i].v)){
            ans += e[i].w;
            fa[find(e[i].u)] = find(e[i].v);
        }
    }
    cout<<ans;
    return 0;
}

Codevs1231

#include<cstdio>
#include<algorithm>
using namespace std;

int fa[100000];
int find(int x){ return fa[x]==x ? x : fa[x]=find(fa[x]);}

struct L{ long long u, v, w; }e[100000];
bool operator < (const L A, const L B){ return A.w<B.w;}

int main(){
    int n, m, count = 0;
    long long ans = 0;
    scanf("%d%d", &n, &m);
    for(int i = 0; i < m; i++)
        scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
    sort(e,e+m);
    for(int i = 0; i <= n; i++) fa[i] = i;
    for(int i = 0; i <= m; i++){
        if(find(e[i].u) != find(e[i].v)){
            fa[find(e[i].u)] = e[i].v;
            ans += e[i].w;
            count++;
        }
        if(count == n-1)break;
    }
    printf("%lld
", ans);
    return 0;
}

三、Prim

0x06子集生成

问:生成0~n所有的子集。
答:

一、增量构造法

#include<iostream>
using namespace std;
int n, A[20];
void dfs(int cur){
    for(int i = 1; i < cur; i++)cout<<A[i]<<" ";
    cout<<"
";
    int s = A[cur-1]+1;
    for(int i = s; i < n; i++){
        A[cur] = i;
        dfs(cur+1);
    }
}
int main(){
    cin>>n;
    A[0] = -1;
    dfs(1);
    return 0;
}

二、位向量法

#include<iostream>
using namespace std;
int n, A[10];
void dfs(int cur){
    if(cur == n){
        for(int i = 0; i < n; i++)
            if(A[i])cout<<i<<" ";
        cout<<"
";
    }else{
        //A[cur] = 1;
        dfs(cur+1);
        A[cur] = 1;
        dfs(cur+1);
        A[cur] = 0;
    }
}
int main(){
    cin>>n;
    dfs(0);
    return 0;
}

三、二进制法

#include<iostream>
using namespace std;
int main(){
    int n;
    cin>>n;
    for(int i = 0; i < (1<<n); i++){
        for(int j = 0; j < n; j++)
            if(i&(1<<j))cout<<j<<" ";
        cout<<"
";
    }
    return 0;
}

0x07ADT抽象数据类型

一、定义

数据数据元素,数据关系以及相关的操作。

二、内容

1. C++的Standard Template Library(模板库)
2. 图论算法的分装

三、STL

<algorithm>
<vector>
<set>
<map>
<stack>
<queue>
<deque>
<list>
<utility><functional><iterator><array><forward_list><unordered_map><memory><numeric><unordered_set>

四、

0x08图的最短路径

一、例题(为形象理解,可无视):
废话:
暑假,小哼准备去一些城市旅游。有些城市之间有公路,有些之间则没有,为了节省经费以及方便计划旅程,小哼希望在出发之前知道任意两个城市之间的最短路径。

数据://本例所有数据均针对有向无环图(DAG)
//代码看不懂的同学可以画一画样例数据的图的说。
4 8
1 2 2
1 3 6
1 4 4
2 3 3
3 1 7
3 4 1
4 1 5
4 3 12
//ans: 0 2 5 4

6 9
1 2 1
1 3 12
2 3 9
2 4 3
3 5 5
4 3 4
4 5 13
4 6 15
5 6 4
//ans:0 1 8 4 13 17

4 5
1 4 9
4 3 8
1 2 5
2 4 6
1 3 7
//ans:0 5 7 9

5 5
2 3 2
1 2 -3
1 5 5
4 5 2
3 4 3
//ans:0 -3 -1 2 4

5 7
1 2 2
1 5 10
2 3 3
2 5 7
3 4 4
4 5 5
5 3 6
//ans:0 2 5 9 9 

二、Floyd-Wallshall

讲道理

本题Code

#include<iostream>
using namespace std;
int n, m, e[110][110];
const int inf = 0xffffff;
int main(){
    cin>>n>>m;
    for(int i = 0; i < n; i++)
        for(int j = 0; j < n; j++)
            e[i][j] = i==j ? 0 : inf;
    for(int i = 0; i < m; i++){
        int a, b, c;   cin>>a>>b>>c;
        e[a-1][b-1] = c;
    }
    for(int k = 0; k < n; k++)
        for(int i = 0; i < n; i++)
            for(int j = 0; j < n; j++)
                if(e[i][j] > e[i][k]+e[k][j])
                    e[i][j] = e[i][k]+e[k][j];
    for(int i = 0; i < n; i++)cout<<e[0][i]<<" ";
    return 0;
}

三、dijkstra

讲道理

本题Code

#include<iostream>
#include<algorithm>
using namespace std;
int n, m, d[110], book[110];
int e[110][110];
const int inf = 0xfffff;
int main(){
    cin>>n>>m;
    for(int i = 0; i < n; i++)
        for(int j = 0; j < n; j++)
            e[i][j] = (i==j ? 0 : inf);
    for(int i = 0; i < m; i++){
        int a, b, c;  cin>>a>>b>>c;
        e[a-1][b-1] = c;
    }
    book[0] = 1;
    for(int i = 0; i < n; i++)d[i] = e[0][i];
    for(int i = 0; i < n; i++){
        int x, y = inf;
        for(int j = 0; j < n; j++)if(!book[j] && d[j]<y)y = d[x=j];
        book[x] = 1;
        for(int j = 0; j < n; j++)d[j] = min(d[j], d[x]+e[x][j]);
    }
    for(int i = 0; i < n; i++)cout<<d[i]<<" ";
    return 0;
}

优化的说

#include<iostream>
#include<algorithm>
using namespace std;
int n, m, d[110], book[110];
int e[110][110];
int u[110], v[110], w[110], first[110], next[110]; //优化1:用邻接表代替邻接矩阵
const int inf = 0xfffff;
int main(){
    cin>>n>>m;
    for(int i = 0; i < n; i++)
        for(int j = 0; j < n; j++)
            e[i][j] = (i==j ? 0 : inf);
    for(int i = 1; i <= n; i++)first[i] = -1;
    for(int i = 1; i <= m; i++){
        cin>>u[i]>>v[i]>>w[i];
        e[u[i]][v[i]] = w[i]; 
        next[i] = first[u[i]];
        first[u[i]] = i;
    }
    book[0] = 1;
    for(int i = 2; i <= n; i++)d[i] = inf;
    for(int i = 1; i <= n; i++){
        int x, y = inf;
        int k = first[i];
        while(k != -1){
            if(!book[u[k]] && d[u[k]]<y)y = w[x=u[k]];
            k = next[k];
        }
        book[x] = 1;
        for(int j = 1; j <= n; j++){
            int k = first[x], t = inf;
            while(k != -1){
                if(v[k]==j){ t = w[k]; break;}
                k = next[k];
            }
            if(d[j]>d[x]+t) d[j] = d[x]+t;
        }
    }
    for(int i = 1; i <= n; i++)cout<<d[i]<<" ";
    return 0;
}

优化的说2

//优化2,3:向量存储+优先队列

四、bellman-ford

讲道理

本题Code

#include<iostream>
using namespace std;
int u[110], v[110], w[110], d[110];
const int inf = 0xfffff;
int main(){
    int n, m;   cin>>n>>m;
    for(int i = 1; i <= m; i++)cin>>u[i]>>v[i]>>w[i];
    for(int i = 2; i <= n; i++)d[i] = inf;
    for(int k = 0; k < n; k++){
        for(int i = 1; i <= m; i++)if(d[v[i]]>d[u[i]]+w[i])
            d[v[i]] = d[u[i]]+w[i];
    }
    for(int i = 1; i <= n; i++)cout<<d[i]<<" ";
    return 0;
}

优化的说

#include<iostream>
using namespace std;
int u[110], v[110], w[110], d[110];
const int inf = 0xfffff;
int main(){
    int n, m;   cin>>n>>m;
    for(int i = 1; i <= m; i++)cin>>u[i]>>v[i]>>w[i];
    for(int i = 2; i <= n; i++)d[i] = inf;
    for(int k = 0; k < n; k++){
        int check = 1;  //优化1:本次没有松弛操作,已经得到了最短路
        for(int i = 1; i <= m; i++)if(d[v[i]]>d[u[i]]+w[i])
            { d[v[i]] = d[u[i]]+w[i]; check = 0;}
        if(check)break;
    }
    for(int i = 1; i <= n; i++)cout<<d[i]<<" ";
    int flag = 0;//功能1:检测负权回路
    for(int i = 1; i <= m; i++)if(d[v[i]]>d[u[i]]+w[i])flag = 1;
    if(flag)cout<<"flag was here 
 and this graph has the ---
";
    return 0;
}

五、bellman-ford

讲道理的说

本题code

六、习题集

0x09漫水填充

一、题型分析

用于DFS求联通块

二、例题

poj2486

题目(待翻译):Due to recent rains, water has pooled in various places in Farmer John's field, which is represented by a rectangle of N x M (1 <= N <= 100; 1 <= M <= 100) squares. Each square contains either water ('W') or dry land ('.'). Farmer John would like to figure out how many ponds have formed in his field. A pond is a connected set of squares with water in them, where a square is considered adjacent to all eight of its neighbors. 
Given a diagram of Farmer John's field, determine how many ponds he has.
输入:
10 12
W........WW.
.WWW.....WWW
....WW...WW.
.........WW.
.........W..
..W......W..
.W.W.....WW.
W.W.W.....W.
.W.W......W.
..W.......W.
输出:
3
//poj2386 - Lake Counting
#include<iostream>
#include<string>
using namespace std;

int n, m, ans;
string a[100];

void dfs(int x, int y){
    for(int i = -1; i <= 1; i++)
        for(int j = -1; j <= 1; j++)
            if(x+i>=0&&x+i<n&&y+j>=0&&y+j<m && a[x+i][y+j]=='W')
                { a[x+i][y+j]='.'; dfs(x+i, y+j);}
}

int main(){
    cin>>n>>m;
    cin.get();
    for(int i = 0; i < n; i++)getline(cin, a[i]);
    for(int i = 0; i < n; i++)
        for(int j = 0; j < m; j++)
            if(a[i][j] == 'W'){ dfs(i, j); ans++;}
    cout<<ans<<"
";
    return 0;
}

UVa572

题目:输入一个m行n列的字符矩阵,统计“@”组成多少个八连通块。
输入:
5 5
****@
*@@*@
*@**@
@@@*@
@@**@
0 0
输出:
2
#include<iostream>
#include<string>
using namespace std;

int m, n;  string a[110];

const int dx[] = {0,0,-1,1,-1,1,-1,1};
const int dy[] = {1,-1,0,0,-1,1,1,-1};
void dfs(int x, int y){
    a[x][y] = '*';
    for(int i = 0; i < 8; i++)
        if(x+dx[i]>=0 && x+dx[i]<m && y+dy[i]>=0 && y+dy[i]<n && a[x+dx[i]][y+dy[i]]=='@')
            dfs(x+dx[i],y+dy[i]);
}

int main(){
    while(cin>>m>>n &&m &&n){
        for(int i = 0; i < m; i++)cin>>a[i];
        int ans = 0;
        for(int i = 0; i < m; i++)
            for(int j = 0; j < n; j++)
                if(a[i][j] == '@'){ dfs(i,j); ans++;}
        cout<<ans<<"
";
    }
    return 0;
}

0x0A全排列问题

一、题目:

题目:给出一个n, 请输出n的所有全排列。
输入:
3
输出:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

二、分析

1.
2.
3.

三、提交

Codevs1294: http://codevs.cn/problem/1294/

四、解答:

解法一、

#include<cstdio>
int n, a[10];
void dfs(int cur){
    if(cur == n){
        for(int i = 0; i < n-1; i++)printf("%d ", a[i]);
        printf("%d
", a[n-1]);
    }else for(int i = 1; i <= n; i++){
        bool ok = true;
        for(int j = 0; j < cur; j++)
            if(a[j] == i)ok = false;
        if(ok){
            a[cur] = i;
            dfs(cur+1);
        }
    }
}
int main(){
    scanf("%d", &n);
    dfs(0);
    return 0;
}

解法二、

#include<iostream>
using namespace std;
int n, a[20], book[20];
void dfs(int cur){
    if(cur == n){
        for(int i = 0; i < n; i++)cout<<a[i]<<" ";
        cout<<"
";
    }else for(int i = 1; i <= n; i++)if(!book[i]){
        a[cur] = i;
        book[i] = 1;
        dfs(cur+1);
        book[i] = 0;
    }
}
int main(){
    cin>>n;
    dfs(0);
    return 0;
}
原文地址:https://www.cnblogs.com/gwj1314/p/9444890.html