zoukankan      html  css  js  c++  java
  • 洛谷P5022&P5049 旅行(及其数据加强版)

    旅行(不是加强版)
    加强版



    加强版数据范围:
    我们注意到
    也就是说要么是个树,要么是个基环树

    60pts

    这60分是个树,可以简单的贪心想到每次都走子树中编号最小的那个,并且把1作为根
    dfs练手题
    还是贴个代码叭

    void dfs1(int now,int fa)
    {
    	if(vis[now])return ;
    	ans1[++t]=now;
    	vis[now]=1;
    	vector<int> qwq;//为了不使存储的点被后面的子树覆盖,所以用vector
    	for(int e=head[now];e;e=ed[e].nxt)
    	{
    		int v=ed[e].to;
    		if(v==fa)continue;
    		qwq.push_back(v);
    	}
           sort(qwq.begin(),qwq.end());//vector的排序
    	int qaq=qwq.size();
    	for(int i=0;i<qaq;i++)
    	  dfs1(qwq[i],now);
    }
    

    100pts

    大多数的解法

    当m==n时,它是一个基环树(即树上挂着一个环的树)
    基环树只要删掉环上的一条边,它就是个树了。所以我们可以枚举删掉哪条边。(需要开个(O_2)
    由于开(O_2)会让你的评测记录显得不优雅,我们要考虑考虑怎么不开(O_2)过掉这道题。
    由于我们要删去环上的边,所以要先找个环,而不是暴力删边再判是否是环上的。这样就可以过去了。

    但是博主脑洞清奇所以并没有用这种做法当然也没有代码

    当然不是
    应某神仙的要求贴上他的代码

    oid dfs3(int from,int fa) {//找环代码
        vis[from]=1;
        for(int i=0;i<a[from].size();i++) {//这里是用vector记录的出边
            int to=a[from][i];
            if(to==fa)
                continue ;
            if(vis[to]) {
                flag=1;//找到了环
                cir1[to]=1;//标记to和from都在环上
                cir1[from]=1;
                u1[++cnt]=from;//记录在环上的点
                v1[cnt]=to;
                return ;
            }
            dfs3(to,from);
            if(flag&&cir1[to])
                if(cir1[from]) {//判断找到了环的“根”(即以1为根时,最靠近1的在环上的点)
                    flag=0;
                    u1[++cnt]=from;
                    v1[cnt]=to;
                    return ;
                } else {
                    cir1[from]=1;
                    u1[++cnt]=from;
                    v1[cnt]=to;
                    return ;
                }
        }
    }
    

    全套代码

    #include<cstdio>
    #include<cstring>
    #include<vector>
    #include<algorithm>
    using namespace std;
    vector<int> a[5010];
    int n,m;
    int res[5010],ans[5010],tot;
    int cir1[5010];
    int u1[10010],v1[10010],cnt;
    int vis[5010];
    int du,dv;
    int flag;
    struct Edge {//咱也不造为啥铁锤妹妹要写前向星(虽然后面也没有用到前向星)
        int from,to;
    }e[5010];
    void dfs2(int u,int fa) {//没有环时的dfs
        if(vis[u])
            return ;
        vis[u]=1;
        ans[++tot]=u;//记录答案
        for(int i=0;i<a[u].size();i++) {
            int v=a[u][i];
            if(v==fa)
                continue ;
            dfs2(v,u);
        }
    }
    void dfs1(int u,int fa) {//有环时的dfs
        if(vis[u])
            return ;
        vis[u]=1;
        res[++tot]=u;
        for(int i=0;i<a[u].size();i++) {
            int v=a[u][i];
            if(v==fa)
                continue ;
            if((u==du&&v==dv)||(u==dv&&v==du))//du,dv为枚举删去的边(见主函数)
                continue ;
            dfs1(v,u);
        }
    }
    void dfs3(int from,int fa) {//找环
        vis[from]=1;
        for(int i=0;i<a[from].size();i++) {
            int to=a[from][i];
            if(to==fa)
                continue ;
            if(vis[to]) {
                flag=1;
                cir1[to]=1;
                cir1[from]=1;
                u1[++cnt]=from;
                v1[cnt]=to;
                return ;
            }
            dfs3(to,from);
            if(flag&&cir1[to])
                if(cir1[from]) {
                    flag=0;
                    u1[++cnt]=from;
                    v1[cnt]=to;
                    return ;
                } else {
                    cir1[from]=1;
                    u1[++cnt]=from;
                    v1[cnt]=to;
                    return ;
                }
        }
    }
    int check() {//比较更优方案
        for(int i=1;i<=n;i++) {
            if(res[i]<ans[i])
                return 1;
            else if(res[i]>ans[i])
                return 0;
        }
        return 0;
    }
    void update() {//更新答案
        for(int i=1;i<=n;i++) {
            ans[i]=res[i];
        }
    }
    int main() {
        scanf("%d%d",&n,&m);
        int u,v;
        for(int i=1;i<=m;i++) {
            scanf("%d%d",&u,&v);
            a[u].push_back(v);//vector选手铁锤妹妹
            a[v].push_back(u);
            e[i].from=u;
            e[i].to=v;
        }
        for(int i=1;i<=n;i++)
            sort(a[i].begin(),a[i].end());
        if(m==n) {
            dfs3(1,0);//找环
            int flag=1;
            for(int i=1;i<=cnt;i++) {
                du=u1[i];dv=v1[i];//枚举删去环上哪条边
                memset(vis,0,sizeof(vis));
                tot=0;
                dfs1(1,0);
                if(tot<n)
                    continue ;
                if(flag) {
                    update();
                    flag=0;
                }
                if(check())
                    update();
            }
            for(int i=1;i<=n;i++) {
                printf("%d ",ans[i]);
            }
        } else {
            dfs2(1,0);
            for(int i=1;i<=n;i++) {
                printf("%d ",ans[i]);
            }
        }
        return 0;
    }
    

    代码转自铁锤妹妹,注释窝加的
    接下来我们谈谈博主清奇的脑洞。
    考虑从环入手选择最优解。
    先来看最简单的环。

    最优解当然是1 2 3 4 5辣。那我们究竟是怎么找出这个顺序的呢?
    首先按照60pts的思路,走编号最小的点。走到2。下一个是5,但是如果我们此时回溯到3,走3-->4-->5这条路,所得的字典序会更小。由此可以得到一个贪心思路:在向编号小的点a走的同时,记录下编号较大的点b的编号。当dfs到一个比b编号大且在环上的点时,回溯到b,由b走过去。
    我们在记录参数(b的编号)(以下称之为cs)的时候,是在环的“根”处(也就是图中的1节点)记录的,所以要先找个环并且记录环的“根”。
    现在把这个环挂到树上。

    最优解是1 2 6 4 3 5 7。我们发现在遍历2的子树时一定要走过7.此时就无法回到3然后从3走了。因此我们对cs要有所改变。(cs初始化为inf)
    cs更新原则:
    如果当前点now是环的“根”root,则cs为它的子树中,在环上且编号较大的那个点
    如果当前点在环上但不是root,且cs不是inf。记录它在环上的子树的编号c,找到最大的不在环上的且大于c的子树编号。如果没有,则cs不变,如果有,cs更新。
    为什么要cs不是inf才能更新呢?因为如果cs是inf且在环上,说明现在是从编号较大的点走过来的,不需要再判断是否回溯。
    找环+对环的dfs:

    int rt,er;//rt就是上文中的root,er记录在找环时是否回溯到了root
    void huan(int now,int fa)
    {
    	vis[now]++;
    	if(vis[now]>1)
    	{hua[now]=1;rt=now;er++;return ;}
    	if(!head[now])return ;
    	bool bj=0;//记录是否有子树在环上
    	for(int e=head[now];e;e=ed[e].nxt)
    	{
    		int v=ed[e].to;
    		if(v==fa)continue;
    		if(now==rt)break;
    		huan(v,now);
    		if(hua[v])bj=1;
    	}
        if(bj)hua[now]=1;//如果有子树在环上,那么now很可能也在环上,特殊情况由下面判断
        if(er==2)hua[now]=0;//er==2说明已经回到了root的父亲节点(祖先节点)
        if(now==rt)er++;
    }
    void dfs2(int now,int fa,int cs)
    {
            if(now>cs)return ;//该回溯了
    	if(vis[now])return ;
    	vis[now]=1;
    	vector<int> qwq;
    	ans1[++t]=now;//记录答案
    	for(int e=head[now];e;e=ed[e].nxt)
    	{
    		int v=ed[e].to;
    		if(v==fa)continue;
    		qwq.push_back(v);
    	}
    	sort(qwq.begin(),qwq.end());//依旧是排序子树的顺序
    	int qaq=qwq.size();
    	if(now==rt)//在root处开始记录cs
    	 for(int i=qaq-1;i>=0;i--)
           	  if(hua[qwq[i]]){cs=qwq[i];break;}	 
            if(now!=rt&&hua[now]&&cs!=inf)
            {
        	  int rwr=inf;
        	  for(int i=qaq-1;i>=0;i--)
        	    if(hua[qwq[i]]){rwr=qwq[i];break;}//rwr记录子树中在环上的点的编号(因为在环上且不是“根”的点有且只有一个子树在环上)
    	  for(int i=qaq-1;i>=0;i--)
    	    if(!hua[qwq[i]]&&qwq[i]>rwr){cs=qwq[i];break;}
    	}
    	for(int i=0;i<qaq;i++)
           {
        	        if((qwq[i]<cs&&now==rt)||(now!=rt&&hua[qwq[i]])) dfs2(qwq[i],now,cs);//如果是开始走编号较小的点或者说now在环上则要带着cs(由于走较大的点的编号的情况在下面更新了cs,所以这么写也是可以的)
    		else dfs2(qwq[i],now,inf);//在走较大的点的时候把cs更新掉
    	}
    }
    

    完整版(无注释):

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<queue>
    #include<map>
    #include<vector>
    #include<cmath>
    using namespace std;
    inline int read()
    {
    	char ch=getchar();
    	int x=0;bool f=0;
    	while(ch<'0'||ch>'9')
    	{
    		if(ch=='-') f=1;
    		ch=getchar();
    	}
    	while(ch>='0'&&ch<='9')
    	{
    		x=(x<<3)+(x<<1)+(ch^48);
            ch=getchar();		
    	}
    	return f?-x:x;
    }
    const int inf=214748364;
    int t,n,m,ans1[500009],cnt,head[500009];
    int vis[500009];
    bool hua[500009];
    struct E{
    	int to,nxt;
    }ed[1000009];
    void add(int fr,int to)
    {
    	ed[++cnt].to=to;
    	ed[cnt].nxt=head[fr];
    	head[fr]=cnt;
    }
    void dfs1(int now,int fa)
    {
    	if(vis[now])return ;
    	ans1[++t]=now;
    	vis[now]=1;
    	vector<int> qwq;
    	for(int e=head[now];e;e=ed[e].nxt)
    	{
    		int v=ed[e].to;
    		if(v==fa)continue;
    		qwq.push_back(v);
    	}
        sort(qwq.begin(),qwq.end());
    	int qaq=qwq.size();
    	for(int i=0;i<qaq;i++)
    	  dfs1(qwq[i],now);
    }
    int rt,er;
    void huan(int now,int fa)
    {
    	vis[now]++;
    	if(vis[now]>1)
    	{hua[now]=1;rt=now;er++;return ;}
    	if(!head[now])return ;
    	bool bj=0;
    	for(int e=head[now];e;e=ed[e].nxt)
    	{
    		int v=ed[e].to;
    		if(v==fa)continue;
    		if(now==rt)break;
    		huan(v,now);
    		if(hua[v])bj=1;
    	}
        if(bj)hua[now]=1;
        if(er==2)hua[now]=0;
        if(now==rt)er++;
    }
    void dfs2(int now,int fa,int cs)
    {
        if(now>cs)return ;
    	if(vis[now])return ;
    	vis[now]=1;
    	vector<int> qwq;
    	ans1[++t]=now;
    	for(int e=head[now];e;e=ed[e].nxt)
    	{
    		int v=ed[e].to;
    		if(v==fa)continue;
    		qwq.push_back(v);
    	}
    	sort(qwq.begin(),qwq.end());
    	int qaq=qwq.size();
    	if(now==rt)
    	 for(int i=qaq-1;i>=0;i--)
           	if(hua[qwq[i]]){cs=qwq[i];break;}	 
        if(now!=rt&&hua[now]&&cs!=inf)
        {
        	int rwr=inf;
        	for(int i=qaq-1;i>=0;i--)
        	 if(hua[qwq[i]]){rwr=qwq[i];break;}
    	    for(int i=qaq-1;i>=0;i--)
    	     if(!hua[qwq[i]]&&qwq[i]>rwr){cs=qwq[i];break;}
    	}
    	for(int i=0;i<qaq;i++)
        {
        	if((qwq[i]<cs&&now==rt)||(now!=rt&&hua[qwq[i]])) dfs2(qwq[i],now,cs);
    		else dfs2(qwq[i],now,inf);
    	}
    }
    int main()
    {
        n=read();m=read();
        for(int i=1;i<=m;i++)
        {
        	int fr=read(),to=read();
        	add(fr,to);
        	add(to,fr);
    	}
        if(m==n-1)dfs1(1,0);
        else 
        {
        	huan(1,0);
            memset(vis,0,sizeof(vis));
            dfs2(1,0,inf);
    	}
        for(int i=1;i<=t;i++)
         printf("%d ",ans1[i]);
        memset(vis,0,sizeof(vis)); 
    }
    

    接下来就是愉快的AC了
    然后博主发现自己的做法好像比较清奇,于是去交了数据加强版,发现也A了
    加强版:把数组改大然后交上就ok了

  • 相关阅读:
    SQL Server 2005 学习笔记之触发器简介[转]
    什么是BCD 码
    关于C# 中的Attribute 特性
    也谈Asp.net 中的身份验证
    SQL Server 2005 创建分区表
    使用SerialPort 对象实现串口拨号器通信[下]
    子角色权限的实现
    SQL Server 中,实现 varbinary 与 varchar 类型之间的数据转换
    TSQL 常用排名函数
    关于ASP.NET 将数据导出成Excel 的总结[中]
  • 原文地址:https://www.cnblogs.com/lcez56jsy/p/11767842.html
Copyright © 2011-2022 走看看