• Evanyou Blog 彩带



    前面几个代码都是部分分代码,最后一个才是AC了的,所以最后一个有详细注释

    这是提高组真题,233有点欧拉回路的感觉


    题目大意:

    一个 连通 图,双向边无重边 , 访问图中所有点并依次记录经过的点的编号。求记录的编号字典序最小。


    题目数据分析

    题目中有 M 条边,而 M 有两种情况: M = N 或者 M = N- 1


    现在我们将 M = N的情况叫做情况一M = N - 1叫做情况二

    情况一和情况二分析

    情况二:我们都知道,一棵生成树有N - 1条边,且要保证连通,那么显而易见,情况二是一棵 生成树 。根据生成树的特性,我们可以得知,情况二 不会形成环

    情况一:情况一,不过是在情况二上多了一条边。但是还是要保证连通、无重边,故情况二是 在一棵生成树上多了一条表——有且只有一个环

    根据题意,可以保证数据 一定 会是以上两种情况。


    那么现在我们的问题是如何解决情况一和情况二。

    情况二

    很简单,一棵生成树,只需要使用爆搜即可。

    由于情况二是生成树,所以个人认为DFS更好使用。

    那怎么搜?

    一颗生成树,是可以以树中任意一个点为 (Root) (根节点)。而题目要求了是要字典序最小,显然 以 1 为根节点开始DFS的答案会更优秀

    此时,只需要从 (1)(N) 循环,判断目前循环到的点与当前到达的点是否连通,并再继续DFS即可。

    血的教训:由于情况二是遍历一棵树,所以不要回溯!

    情况二代码如下:

    #include<bits/stdc++.h>
    using namespace std;
    
    const int MAXN = 5000 + 10;
    
    int n,m;
    bool dis[MAXN][MAXN];
    int in[MAXN],ans[MAXN],cnt;
    bool b[MAXN];
    
    inline int read(){
        int f = 1, x = 0;
        char c = getchar();
    
        while (c < '0' || c > '9')
        {
            if (c == '-')
                f = -1;
            c = getchar();
        }
    
        while (c >= '0' && c <= '9')
        {
            x = x * 10 + c - '0';
            c = getchar();
        }
    
        return f * x;
    }
    
    void dfs(int now){
        ans[++cnt] = now;
        b[now] = true;
        for(int i = 1;i <= n; i++){
            if(b[i])continue;
            if(!dis[now][i])continue;
            b[i] = true;
            dfs(i);
        }
    }
    
    int main(){
        n = read(),m = read();
        for(int i = 1;i <= m; i++){
            int x = read(),y = read();
            dis[x][y] = dis[y][x] = true;
        }
        dfs(1);
        for(int i = 1;i <= n; i++){
            cout<<ans[i]<<" ";
        }
    }
    

    情况二只占60分。那么其他40分就是情况一了

    题目都要由简入难。情况二解决,情况一就有思路可想=借鉴了。

    现在情况一不过是在情况二上面多了一个环。还可以使用DFS么?很明显不行。

    由于所有点只能访问一次(退回来的不算),环中必须会有一条边无法访问。

    为什么

    因为对于一棵树,dfs是每个节点与其父亲连接的边都可以搜到的;

    但是现在多了一条边,又要满足题目要求——每个点除了第一次访问和回溯外,不能再次访问

    所以在情况二上多加了一条边,那么一定有一条边无法访问到。

    所以呢?

    我们只需要找到唯一的环,在环内任意枚举一条边,删去那一条边,再运行情况二的DFS,最后取最优解即可

    注意:情况一DFS与情况二DFS不同,情况一由于有环,所以要回溯。

    那么我们现在的问题就是要找环。并准确知道哪些点在环内。

    如何找环?

    并查集?很明显不行。它只能判断两个点是否是一个父亲且一个点在不在环内,很麻烦求出准确的环。

    Tarjan?太麻烦了......

    那么就用——拓扑排序!

    并查集、Tarjan、拓扑排序之后都会在本人Blog中介绍

    我们拓扑排序删边时,只要标记当前队列的头,拓扑之后那些没被标记过的,也就是没被拓扑排序的、在环内的点。

    但是拓扑排序只有入度为0的点才加入队列么?题目说是无向图,一条边两个点入度都++,那不就没有入度为0的点了么?

    没关系,我们只需要将入度和出度加起来当做度数来,再判断度数是不是1即可。

    为什么?

    因为拓扑排序在累计入度的时候,环的入口入度一定为 (3) ,一个父亲,另外的是环内的点自带的入度。删去与环连接的那一条边,入口点的入度还是会有大于一,故无法进队处理。

    那么情况一和情况二的合集代码如下

    先不要提交下面的代码!因为没有前面说的注释,所有后面还有没讲完的

    #include<bits/stdc++.h>
    using namespace std;
    
    const int MAXN = 5000 + 10;
    
    int n,m,cur;
    int in[MAXN],ans[MAXN];
    int res[MAXN];
    
    
    vector<int>nei[MAXN];
    bool dis[MAXN][MAXN];
    struct Line{
        int xx,yy;
    }line[MAXN];
    
    bool b[MAXN],use[MAXN];
    
    inline int read(){
        int f = 1, x = 0;
        char c = getchar();
    
        while (c < '0' || c > '9')
        {
            if (c == '-')
                f = -1;
            c = getchar();
        }
    
        while (c >= '0' && c <= '9')
        {
            x = x * 10 + c - '0';
            c = getchar();
        }
    
        return f * x;
    }
    
    void init(){
        n = read(),m = read();
    
        for(int i = 1;i <= m; i++){
            int x = read(),y = read();
            dis[x][y] = dis[y][x] = true;
            nei[x].push_back(y);
            nei[y].push_back(x);
            in[x]++,in[y]++;
            line[i] = (Line){x,y};
        }
        return;
    }
    
    void out(){
        for(int i = 1;i <= n; i++){
            cout<<ans[i]<<" "; 
        }
        cout<<"
    ";
        return;
    }
    
    void new_ans(){
        for(int i = 1;i <= n; i++){
            ans[i] = res[i];
        }
    }
    
    void work() {
        if(ans[1] == 0){
        	new_ans();
        	//out();
        	return;
        }
    
        for(int i = 1; i <= n; i++) {
            if(res[i] == ans[i])
                continue;
            else if(res[i] < ans[i]) {
                new_ans();
                //out();
                return;
            }
            //out();
            
            return;
        }
    }
    
    void Topo(){
        queue<int>q;
        
        for(int i = 1;i <= n; i++){
            if(in[i] == 1){
                q.push(i);
                use[i] = true;
            }
        }
        
        while(!q.empty()){
            int tot = q.front();
            int len = nei[tot].size();
            
            q.pop();
            use[tot] = true;
            
            for(int i = 0;i < len; i++){
                int next = nei[tot][i];
                in[next]--;
                
                if(in[next] == 1){
                    q.push(next);
                    use[next] = true;
                }
            }
        }
    }
    
    void dfs_plan_1(int now){
        ans[++cur] = now;
        b[now] = true;
        for(int i = 1;i <= n; i++){
            if(b[i])continue;
            if(!dis[now][i])continue;
            b[i] = true;
            dfs_plan_1(i);
        }
    }
    
    void dfs(int now){
        res[++cur] = now;
        b[now] = true;
        for(int i = 1;i <= n; i++){
            if(b[i])continue;
            if(!dis[now][i])continue;
            b[i] = true;
            dfs(i);
            b[i] = false;
        }
        b[now] = false;
    } 
    
    int main(){
        init();
        
        for(int i = 1; i <= n; i++) {
            sort(nei[i].begin(), nei[i].end());
        }
        
        if(m == n - 1){
            dfs_plan_1(1);
            out();
            return 0;
        }
    
        Topo();
    
        for(int i = 1;i <= m; i++){
            int x = line[i].xx;
            int y = line[i].yy;
    
            if(use[x] || use[y])continue;
    
            dis[x][y] = dis[y][x] = false;
            cur = 0;
    
            dfs(1);
    
            work();
            dis[x][y] = dis[y][x] = true;
        }
        
        out();
        return 0;
    }
    

    为什么TLE了几个点?

    不是叫你别提交么

    因为这种做法枚举了所有点,会导致超时,所以我们现在只需要维护每个点和它相邻的边,枚举那些边就好。


    那么现在思路非常明确了:

    那么其实上面就是一个重要的知识——

    基环图

    没完,代码下面还有东西

    #include<bits/stdc++.h>//万能头 
    using namespace std;
    
    const int MAXN = 5000 + 10;//定义常量 
    
    int n,m,cur;//n、m与题目相对应,cur记录答案累加用途 
    int in_out[MAXN],ans[MAXN];//ans为答案,in_out为度数(即入读加上出度) 
    int res[MAXN];//用于临时存目前计划,在更新ans与res判断 
    
    
    vector<int>nei[MAXN];//动态数组用于记录邻居,这里用于优化时间复杂度 
    bool dis[MAXN][MAXN];//邻接矩阵,用来优化判断两个点的边目前又没有删 
    struct Line{//记录边的结构体 
    	int xx,yy;//起始点和终止点 
    }line[MAXN];
    
    bool b[MAXN],use[MAXN];//b用于在DFS时看是否搜过了 ,use记录该店是否在环内 
    
    inline int read(){//读入优化 
        int f = 1, x = 0;
        char c = getchar();
    
        while (c < '0' || c > '9')
        {
            if (c == '-')
                f = -1;
            c = getchar();
        }
    
        while (c >= '0' && c <= '9')
        {
            x = x * 10 + c - '0';
            c = getchar();
        }
    
        return f * x;
    }
    
    void init(){//读入函数 
    	n = read(),m = read();//读入点和边 
    
    	for(int i = 1;i <= m; i++){//读入边 
    		int x = read(),y = read();
    		dis[x][y] = dis[y][x] = true;//将x与y的邻接矩阵更新 
    		nei[x].push_back(y);//将y压入x的邻居 
    		nei[y].push_back(x);//将x压入y的邻居
    		in_out[x]++,in_out[y]++;//将x和y的度数++ 
    		line[i] = (Line){x,y};//保存边 
    	}
    	
    	//for(int i = 1;i <= n; i++){
    	//	for(int j = 1;j <= n; j++){
    	//		cout<<dis[i][j]<<" ";
    	//	}
    	//	cout<<endl;
    	//}
    	//调试函数 
    	
    	return;
    }
    
    void out(){//输出函数,单独拿出来是用于调试用的 
    	for(int i = 1;i <= n; i++){
    		cout<<ans[i]<<" "; //循环输出答案 
    	}
    	cout<<"
    ";
    	return;
    }
    
    void new_ans(){//更新答案 
    	for(int i = 1;i <= n; i++){
    		ans[i] = res[i];
    	}
    }
    
    void work() {//用于判断目前方案与答案哪个更优 
        if(ans[1] == 0){//如果目前答案为空,直接记录 
        	new_ans();//更新答案 
        	//out();
        	return;//这里一定要返回 
        }
    
        for(int i = 1; i <= n; i++) {//逐次比较哪个更优 
            if(res[i] == ans[i])//如果相同则继续 
                continue;
            else if(res[i] < ans[i]) {//当前方案更优 
                new_ans();//更新 
                //out();
                return;
            }
    	//	out();
    		
            return;
        }
    }
    
    void Topo(){//基环图(与拓扑排序差不多) 
    	queue<int>q;//定义队列 
    	
    	for(int i = 1;i <= n; i++){//将度数为1的雅图队列 
    		if(in_out[i] == 1){
    			q.push(i);
    			use[i] = true;//标记不在环中 
    		}
    	}
    	
    	while(!q.empty()){//还可以继续做 
    		int tot = q.front();//取队首 
    		int len = nei[tot].size();
    		
    		q.pop();
    		use[tot] = true;//标记不在环中 
    		
    		for(int i = 0;i < len; i++){//依次删除度数 
    			int next = nei[tot][i];//下一个点 
    			in_out[next]--;
    			
    			if(in_out[next] == 1){//压入队列 
    				q.push(next);
    				use[next] = true;
    			}
    		}
    	}
    }
    
    void dfs_plan_1(int now){//用于情况二的DFS,这里不要回溯 
    	ans[++cur] = now;//直接更新答案 
        b[now] = true;//标记 
        int len = nei[now].size(); 
        
        for(int i = 0;i < len; i++){//循环
    		int next = nei[now][i];
    	 
            if(b[next])continue;
            if(!dis[now][next])continue;
            b[next] = true;
            dfs_plan_1(next);
        }
    }
    
    void dfs(int now){//用于情况一的DFS,这里要回溯 
    	res[++cur] = now;//更新当前方案 
    	b[now] = true;
    	int len = nei[now].size();	
    	for(int i = 0;i < len; i++){
    		int next = nei[now][i];
    		
    		if(b[next])continue;
    		//cout<<now<<" "<<next<<" "<<dis[i][next]<<endl;
    		if(!dis[now][next])continue;
    		b[next] = true;
    		//cout<<next;				
    		dfs(next);
    		b[next] = false;
    	}
    	b[now] = false;
    	//cout<<"end."<<endl;
    } 
    
    int main(){
    	init();//读入 
    	
    	for(int i = 1; i <= n; i++) {
    		//这里很巧妙,从小到大排列邻居,优化 
            sort(nei[i].begin(), nei[i].end());
        }
        
    	if(m == n - 1){//情况二 
    		dfs_plan_1(1);
    		out();
    		return 0;
    	}
    
    	//情况一 
    
    	Topo();//基环图 
    
    	for(int i = 1;i <= m; i++){//枚举删边 
    		int x = line[i].xx;
    		int y = line[i].yy;
    
    		if(use[x] || use[y])continue;//有一个点不在环中就跳过 
    
    		//cout<<i<<"line:"<<x<<" "<<y<<" "<<dis[x][y]<<endl;
    
    		dis[x][y] = dis[y][x] = false;//删边 
    		cur = 0;
    
    		dfs(1);//DFS 
    
    		work();//更新答案 
    		dis[x][y] = dis[y][x] = true;//恢复边 
    	}
    	
    	out();//输出 
    	return 0;
    }
    

    基环图的处理方法

    来都来了,听听吧

    基换图如上面说的,就是一颗树多了一条边,形成了唯一的环。处理方法也会像上面说的,Topo排序找环、DFS即可

  • 相关阅读:
    5、流程控制
    4、字典和元祖
    3、列表操作
    2、字符串和数据类型
    1.标识符练习
    使用xpath提取页面所有a标签的href属性值
    网页提取所有邮箱
    正则表达式
    提取包含QQ的文本为QQ邮箱
    python继承小demo
  • 原文地址:https://www.cnblogs.com/CJYBlog/p/LG5022.html
Copyright © 2020-2023  润新知