搜索(八皇后) 洛谷P1219

//搜索的实现。 
//优化dfs1。 
//八皇后。 
//n个皇后在不在同一条直线或同一条斜线,一共多少方案。
#include<iostream>
#include<cstdio>
using namespace std; 
int n,ans=0;  
int pos[maxn];//pos[i]代表第i行的皇后放在了第pos[i]列。 
void dfs(int now){//现在要去尝试放第now行的皇后。 
    if(now>n){
        ans++;
        return;
    } 
    for(int j=1;j<=n;j++){//第now行的皇后放在第j列。 
        bool able=true;
        for(int i=1;i<now;i++){//枚举第i行的皇后。 
            if(pos[i]=j||abs(now-i)==abs(j-pos[i])){
                able=false;
                break;
            }
        }
        if(able){
            pos[now]=j;
            dfs(now+1);
        }
    }
} 
int main(){
    n=13;//3.68s。 
    dfs(1);
    cout<<ans<<endl;
    return 0; 
} 

 

 

优化dfs2。

判断第i行、第i条斜线上有没有皇后。

斜线=i+j-1>。

#include<iostream>
#include<cstdio>
using namespace std; 
int n,ans=0; 
int pos[20];//pos[i]代表第i行的皇后放在了第pos[i]列。 
bool col[20];//col[i] 代表第i条的直线有没有皇后。 
bool xie1[50];//xie[1] 代表第i条从左上到右下的斜线有没有皇后。
bool xie2[50];//xie[2] 代表第i条从右上到左下的斜线有没有皇后。 
void dfs(int now){//现在要去尝试放第now行的皇后。 
    if(now>n){
        ans++;
        return;
    } 
    for(int j=1;j<=n;j++){//第now行的皇后放在第j列。
        int ibx1=j-now+n;//从左上到右下的斜线编号。 
        int ibx2=now+j-1;//从右上到左下的斜线编号。 
        if(!col[j]&&!xie1[idx1]&&!xie2[idx2]){
            pos[now]=j;
            col[j]=xie1[idx1]=xie2[idx2]=true; 
            dfs(now+1);
            col[j]=xie1[idx1]=xie2[idx2]=false;
        }
    }
} 
int main(){
    n=13;//0.6141s。 
    n=14;//3.494s。
    dfs(1);
    cout<<ans<<endl;
    return 0; 
}

 

 

图论:
图的概念 由点和边构成的元素
边:如果边都有方向 我们叫它有向图 没方向叫无向图
一、图的一些基本概念:

1.度:一个顶点连了几条边 就是它多少度
2.有向图里的入度和出度:连向自己的度就是入度 往外连得就是出度
3.有向图里的自环:既是入度又是出度
4.路径:只要沿着边走叫做路径 如:1 -> 2 -> 3 -> 2 -> 1
5.简单路径:不能走重复点和边的路径 如:1 -> 2 -> 3u
6.环:起点等于终点的路径(绕起来)  如:1 -> 2 -> 1
7.简单环:起点等于终点且保证除起点和终点外不经过重复的点和边

图的分类方式:1.树:无向、无环、连通(任意两个点之间都可以互相走到)

如果有一个n个点的树,它一定会有n - 1条边

2. 森林:无向、无环(很多棵树)形象!

3.有向树:无环、连通

外向树:如果所有边都指向外边

内向树:如果所有边都指向一点

注意:有可能一棵树既是外向树又是内向树!

4.章鱼图:无向、连通、有环且只有一环

章鱼图的每一个点向往外延伸 一定是一棵树。如果一个章鱼图有n个点,它就一定有n条边

章鱼图想要变成树,只需要去掉环上的一条边

一棵树想要变成章鱼图,只需加一条边

5.DAG  ——>   有向无环图

6.二分图(无向图)  --> 能够把所有的边分为两部分不要求联通

见图:

树和森林一定是二分图

判断:没有奇环(即奇数个顶点组成的环) 就是二分图

 

二、代码实现存

1.邻接矩阵存图

 

int z[101][101];
//z[i][j] 代表i到j那条边的长度 
int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        int s,e,d;
        cin >> s >> e >> d;//起点为s,终点为e,长度为d
        z[s][e] = d;//有向图 
        //z[e][s] = d; 无向图 
    } 
}

 

坏处:内存消耗比较大

好处:简洁、快(因为只要知道i到j的那条边的信息 O(1))

 

2.边表存图

vector< pair<int,int> > z[maxn];
//z[i]:代表 所有以i为起点的所有边
//z[i][j] :代表所有以i为起点的第j条边
//z[i][j].forst:代表i为起点的第j条边的终点
//z[i][j].second: 代表i为起点的第j条边的起点 
void add_edge(int s, int e, int d){
    z[s].push_back(make_pair(e,d));
    //push_back:向vector的末尾添加一个元素 
}
int main(){
    cin >> n >> n;
    for(int i = 1; i <= n; i++){//添加一条从s出发,到e 长度为d的边 
        int s,e,d;
        cin >> s >> e >> d;
        add_edge(s,e,d);//添加一条边 STL函数 
        add_edge(e,s,d);//无向图 
    }
    for(int i = 1; i <= n; i++)
        for(int j = 0; j < z[i].size();j++){
            int e = z[i][j].first;
            int d = z[i][j].second;
            //代表当前的边 是从i开始的第j条边 终点为e 长度为d 
        }
}

优点:改善了上一种方法的内存

缺点:慢

 

三、解决一些问题

1.一张图 有n个点 me 条边 并且是一张无向图 请判断是否是二分图(二分图染色)

思路:染色法从一号点开始 将其染色为1 与它相邻的点应当是第二部分 ;与第二部分相邻的点就是第一部分。如果能够成功将所有的点都染色 就是二分图 如果一个点的两边的点颜色相同就不是二分图

注意:这里其实有一个坑,我们从一号点开始 如果我们的图不连通就无法找到所有点,所以需要枚举

 

vector<int > z[maxn];
//z[i]:代表 所有以i为起点的所有边
//z[i][j] :代表所有以i为起点的第j条边
//z[i][j].forst:代表i为起点的第j条边的终点
//z[i][j].second: 代表i为起点的第j条边的起点 
void add_edge(int s, int e){
    z[s].push_back(e);
    //push_back:向vector的末尾添加一个元素 
}
int col[maxn];
//col[i] == 0 代表还没有染色
//否则代表第i个点的颜色
 
int main(){
    cin >> n >> n;
    for(int i = 1; i <= n; i++){//添加一条从s出发,到e 长度为d的边 
        int s,e;
        cin >> s >> e;
        add_edge(s,e);//添加一条边 STL函数 
        add_edge(e,s);//无向图 
    }
    for(int i = 1; i <= n; i++){
        if(col[i] != 0) continue;
        col[1] = 1;
        queue<int> q;//可以改变周围点颜色的点
        q.push(1);
        while(q.size()){
            int i = q.front();
            q.pop();
            for(int j = 0; j < z[i].size(); j++){
                int k = z[i][j];
                //i -> k 的一条边 
                if(!col[k]){
                    col[k] = 3 - col[i];
                    q.push(k);
                }
                else{
                    if(col[k] !== col[i]){
                        cout << "不是二分图!" << endl;
                        exit(0);
                    }
                }
            }
        } 
    }
    cout << "是二分图 !"<< endl;//顺利完成整个程序 
    return 0; 
}

 

 

2.拓扑排序(针对DAG

保证排完序之后原图当中存在的边都是从左向右指的,叫做拓扑排序。

如果一个点的入度为零 应把它放在最前面 把每次入读为0的点更新实现:

vector<int > z[maxn];
//z[i]:代表 所有以i为起点的所有边
//z[i][j] :代表所有以i为起点的第j条边
//z[i][j].forst:代表i为起点的第j条边的终点
//z[i][j].second: 代表i为起点的第j条边的起点 
void add_edge(int s, int e){
    z[s].push_back(e);
    //push_back:向vector的末尾添加一个元素 
}
int col[maxn];
//col[i] == 0 代表还没有染色
//否则代表第i个点的颜色
int in[maxn];//代表i点的入度 是多少 
int main(){
    cin >> n >> n;
    for(int i = 1; i <= n; i++){//添加一条从s出发,到e 长度为d的边 
        int s,e;
        cin >> s >> e;
        add_edge(s,e);//添加一条边 STL函数 
        in[e] ++;
    }
    int cnt = 0;
    for(int i = 1; i <= n; i++)
        if(in[i] == 0) resault[++cnt] = i; 
    for(int i = 1; i <= n; i++){//当前要取出的拓扑排序的结果中的第i个点 
        int p = result[i];
        for(int j = 0; j < z[p].size(); j++){
            int q = z[p][j];//p -> q的边 
            in[q] --;
            if(in[q] == 0) result[++cnt] = q;
        }
    }    
    return 0; 
}

 插播一点小概念:每个节点在第几层叫做这个节点的深度

祖先:在i上面的节点

lca(i,j) = i 和j的最近公共祖先

Q;给出树 求出lca(i,j)

vector<int > z[maxn];
//z[i]:代表 所有以i为起点的所有边
//z[i][j] :代表所有以i为起点的第j条边
//z[i][j].forst:代表i为起点的第j条边的终点
//z[i][j].second: 代表i为起点的第j条边的起点
void add_edge(int s, int e){
    z[s].push_back(e);
    //push_back:向vector的末尾添加一个元素
}
void dfs(int i, int j){//i 的父节点是j
    f[i] = j;
    depth[i] = depth[j] + 1;
    for(int k = 0; k < z[i].size();k++){
        int l = z[i][k];//这条边是从 i -> l的边
        if(l != j) dfs(l,i);
    }
}
void get_lca(int p1, int p2){
    while(p1 != p2){
        if(depth[p1] < depth[p2]) swap(p1,p2);
        p1 = f[p1];
    }
    return p1;
}

int depth[maxn], f[maxn];//深度 父节点
int main(){
    cin >> n >> n;
    for(int i = 1; i <= n; i++){//添加一条从s出发,到e 长度为d的边
        int s,e;
        cin >> s >> e;
        add_edge(s,e);//添加一条边 STL函数
        add_edge(e,s);
    }
    dfs(1,0);
    return 0;
}

优化:倍增求lca

int depth[maxn], f[maxn][20];//深度  从i 
vector<int > z[maxn];
//z[i]:代表 所有以i为起点的所有边
//z[i][j] :代表所有以i为起点的第j条边
//z[i][j].forst:代表i为起点的第j条边的终点
//z[i][j].second: 代表i为起点的第j条边的起点 
void add_edge(int s, int e){
    z[s].push_back(e);
    //push_back:向vector的末尾添加一个元素 
}
void dfs(int i, int j){//i 的父节点是j 
    f[i][0] = j;
    for(int k = 1; k < 20; k++){
        f[i][k] = f[f[i][k - 1] ][k - 1];
    }
    
    depth[i] = depth[j] + 1;
    for(int k = 0; k < z[i].size();k++){
        int l = z[i][k];//这条边是从 i -> l的边
        if(l != j) dfs(l,i); 
    }
}
void get_lca(int p1, int p2){
    //把p1 p2深度调整成一样的
    else if(depth[p1] < depth[p2]) swap(p1, p2);
    for(int i = 19; i >= 0; i--)
        if(depth[f[p1][i]] >= depth[p2]) p1 = f[p1][i];
    if(p1 == p2) return p1;
    for(int i = 19; i >= 0; i--)
        if(f[p1][i] != f[p2][i]) p1 = f[p1][i], p2 = f[p2][i];
    return f[p1][0];
}

int main(){
    cin >> n >> n;
    for(int i = 1; i <= n; i++){//添加一条从s出发,到e 长度为d的边 
        int s,e;
        cin >> s >> e;
        add_edge(s,e);//添加一条边 STL函数 
        add_edge(e,s);
    }
    dfs(1,0);
    return 0; 
}

 

posted on 2023-07-11 14:46  Slz_konnyaku  阅读(26)  评论(0)    收藏  举报