• 【洛谷2403】[SDOI2010] 所驼门王的宝藏(Tarjan+dfs遍历)


    点此看题面

    大致题意: 一个由(R*C)间矩形宫室组成的宫殿中的(N)间宫室里埋藏着宝藏。由一间宫室到达另一间宫室只能通过传送门,且只有埋有宝藏的宫室才有传送门。传送门分为3种,分别可以到达同行的任一宫室(横天门)同列的任一宫室(纵寰门)以该宫室为中心周围8个的任一宫室(自 由 门)。现在你可以从任一宫室开始寻宝,并可以在任一宫室结束寻宝,请求出最多可获得的宝藏数目(每个宝藏只能获得一次)。

    一个简单的想法

    显然,我们可以将每个宫室与它能到达的宫室之间连一条边。由于可能会出现环,我们就需要(Tarjan)来把环缩点,缩点的过程中要注意记录每个环上的宫室数目。

    缩完点后,我们就可以从每个入度为1的点开始对图进行遍历,求出最多能走过的宫室数。而这个步骤可以用dfs轻松实现。

    这样就好了吗?

    不,还有一些细节。

    小技巧优化

    首先,我们要注意的是,直接把每个宫室与它能到达的宫室之间两两建边,建出的边的规模是(O(N^2))的,而(N≤100000),这么多边我们存不下。虽然洛谷数据水,这样也能过。

    这时就要用到一个小技巧:对于同一行的横天门,我们不需要将其两两之间连边,只要保证最后能够连成一个即可;而该行其他类型的门,也不需要将每一个横天门向其连边,只要让第一个出现的横天门向所有其他类型的门连边即可。同理,对于同一行的纵寰门,也可以进行同样的处理,让边数从(O(n^2))降到了(O(n))大大减少了边数。

    还有,就算你进行了这样的操作,最后还是有可能会TLE,这时,我们可以发现,好像时间复杂度最大的就是最后的dfs遍历了。我们可以考虑用记忆化,用(vist[i])来记录从缩点后编号为(i)的点出发,最多能得到的宝物数目即可。

    这样,就可以AC了。

    代码

    #include<bits/stdc++.h>
    #define max(a,b) ((a)>(b)?(a):(b))
    #define min(a,b) ((a)<(b)?(a):(b))
    #define LL long long
    #define N 100000
    using namespace std;
    typedef pair<int,int> point;
    int n,r,c,d=0,ee=0,nee=0,cnt=0,ans=0,top=0,lnk[N+5],nlnk[N+5],dfn[N+5],low[N+5],vis[N+5],Stack[N+5],sum[N+5],In[N+5],vist[N+5];
    struct door
    {
        int x,y,Type,pos,col;
    }a[N+5];
    struct edge
    {
        int to,nxt;
    }e[2*N+5],ne[2*N+5];
    map<point,int> mp;
    inline char tc()
    {
        static char ff[100000],*A=ff,*B=ff;
        return A==B&&(B=(A=ff)+fread(ff,1,100000,stdin),A==B)?EOF:*A++;
    }
    inline void read(int &x)
    {
        x=0;int f=1;char ch;
        while(!isdigit(ch=tc())) if(ch=='-') f=-1;
        while(x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc()));
        x*=f;
    }
    inline void write(int x)
    {
        if(x<0) putchar('-'),x=-x;
        if(x>9) write(x/10);
        putchar(x%10+'0');
    }
    inline void add(int x,int y)//添加一条新边
    {
        if(x^y) e[++ee].to=y,e[ee].nxt=lnk[x],lnk[x]=ee;
    }
    inline void nadd(int x,int y)//添加一条缩点后的新边
    {
        if(x^y) ne[++nee].to=y,ne[nee].nxt=nlnk[x],nlnk[x]=nee;
    }
    inline bool cmp_x(door x,door y)//以行为第一关键字进行sort,使每一行都是横天门在最前面
    {
        if(x.x^y.x) return x.x<y.x;//判断是否在同一行,不是同一行的尽量让行小的在前面
        if(x.Type==1&&y.Type^1) return true;//对于同行的,让横天门在最前面
        if(x.Type^1&&y.Type==1) return false;
        return x.y<y.y;//如果都是或都不是横天门,则列小的在前面
    }
    inline bool cmp_y(door x,door y)//以列为第一关键字进行sort,使每一列都是纵寰门在最前面
    {
        if(x.y^y.y) return x.y<y.y;//判断是否在同一列,不是同一列的尽量让列小的在前面
        if(x.Type==2&&y.Type^2) return true;//对于同列的,让纵寰门在最前面
        if(x.Type^2&&y.Type==2) return false;
        return x.x<y.x;//如果都是或都不是纵寰门,则行小的在前面
    }
    inline bool cmp_z(door x,door y)//对自 由 门的特殊处理,让自 由 门在最前面
    {
        if(x.Type==3&&y.Type^3) return true;//让自 由 门在最前面
        if(x.Type^3&&y.Type==3) return false;
        return x.pos<y.pos;//如果都是或都不是自 由 门,则编号小的在前面
    }
    inline bool cmp_pos(door x,door y)//以编号为第一关键字进行sort,让编号小的在前面,变回读入时的顺序
    {
        return x.pos<y.pos;
    }
    inline void Tarjan(int x)//利用Tarjan缩点
    {
        dfn[x]=low[x]=++d,Stack[++top]=x,vis[x]=1;//记录当前节点的dfs序与当前节点所能到达的dfs序最小的点,将当前节点加入栈中,并标记当前节点在栈中
        for(register int i=lnk[x];i;i=e[i].nxt)//枚举从当前节点出发的每一条边
        {
            if(!dfn[e[i].to]) Tarjan(e[i].to),low[x]=min(low[x],low[e[i].to]);//如果这个节点没访问过,就先对这个节点进行操作,然后更新当前节点能到达的dfs序最小的点
            else if(vis[e[i].to]) low[x]=min(low[x],low[e[i].to]);//否则,如果这个点在栈中,就进行更新
        }
        if(low[x]==dfn[x])//如果当前节点就是当前节点能到达的dfs序最小的点,则对当前强连通分量进行缩点
        {
            sum[a[x].col=++cnt]=1,vis[x]=0;//给当前节点加入一个新的强连通分量,并将这个新的强连通分量的大小赋值为1,标记当前节点已出栈
            while(Stack[top]^x) ++sum[a[Stack[top]].col=cnt],vis[Stack[top--]]=0;//将栈中当前节点之上的节点一一弹出,并更新这个新的强连通分量的大小
            --top;//将当前节点弹出
        }
    }
    inline void dfs(int x)//dfs遍历缩点后的图
    {
        for(register int i=nlnk[x];i;i=ne[i].nxt)//枚举每一条边
        {
            if(!vist[ne[i].to]) dfs(ne[i].to);//如果这个节点未被访问过,就访问该节点
            vist[x]=max(vist[x],vist[ne[i].to]+sum[ne[i].to]);//更新从当前节点出发能得到的最多的宝物数目
        }
    }
    int main()
    {
        register int i,j;
        for(read(n),read(r),read(c),i=1;i<=n;++i) 
            read(a[i].x),read(a[i].y),read(a[i].Type),a[i].pos=i,mp[make_pair(a[i].x,a[i].y)]=i;
        int fst;
        sort(a+1,a+n+1,cmp_x);//对接下来对横天门的操作的预处理
        for(i=1,fst=0;i<=n;++i)
        {
            while(i<=n&&a[i].Type^1) ++i;//只要当前门不是横天门,就跳过这个门
            if(i>n) continue;
            fst=i;//将i标记为该行第一个横天门
            while(i<=n&&a[i].x==a[i+1].x&&a[i+1].Type==1) add(a[i].pos,a[i+1].pos),++i;//将前一个横天门与当前横天门连边
            add(a[i].pos,a[fst].pos);//将最后一个横天门与第一个横天门连边,形成一个环
            while(i<=n&&a[i].x==a[i+1].x) add(a[fst].pos,a[++i].pos);//将该行第一个横天门与该行其他类型的门连边
        }
        sort(a+1,a+n+1,cmp_y);//对接下来对纵寰门的操作的预处理
        for(i=1,fst=0;i<=n;++i)
        {
            while(i<=n&&a[i].Type^2) ++i;//只要当前门不是纵寰门,就跳过这个门
            if(i>n) continue;
            fst=i;//将i标记为该列第一个纵寰门
            while(i<=n&&a[i].y==a[i+1].y&&a[i+1].Type==2) add(a[i].pos,a[i+1].pos),++i;//将前一个纵寰门与当前纵寰门连边
            add(a[i].pos,a[fst].pos);//将最后一个纵寰门与第一个纵寰门连边,形成一个环
            while(i<=n&&a[i].y==a[i+1].y) add(a[fst].pos,a[++i].pos);//将该列第一个纵寰门与该列其他类型的门连边
        }
        sort(a+1,a+n+1,cmp_z);//对接下来对自 由 门的操作的预处理
        for(i=1;i<=n&&a[i].Type==3;++i)//枚举每一个自 由 门
        {
          	//枚举每个自 由 门周围的8个宫室,将这个门与周围有宝藏的宫室相连
            if(mp[make_pair(a[i].x-1,a[i].y)]) add(a[i].pos,mp[make_pair(a[i].x-1,a[i].y)]);
            if(mp[make_pair(a[i].x+1,a[i].y)]) add(a[i].pos,mp[make_pair(a[i].x+1,a[i].y)]);
            if(mp[make_pair(a[i].x,a[i].y-1)]) add(a[i].pos,mp[make_pair(a[i].x,a[i].y-1)]);
            if(mp[make_pair(a[i].x,a[i].y+1)]) add(a[i].pos,mp[make_pair(a[i].x,a[i].y+1)]);
            if(mp[make_pair(a[i].x-1,a[i].y-1)]) add(a[i].pos,mp[make_pair(a[i].x-1,a[i].y-1)]);
            if(mp[make_pair(a[i].x+1,a[i].y+1)]) add(a[i].pos,mp[make_pair(a[i].x+1,a[i].y+1)]);
            if(mp[make_pair(a[i].x+1,a[i].y-1)]) add(a[i].pos,mp[make_pair(a[i].x+1,a[i].y-1)]);
            if(mp[make_pair(a[i].x-1,a[i].y+1)]) add(a[i].pos,mp[make_pair(a[i].x-1,a[i].y+1)]);
        }
        sort(a+1,a+n+1,cmp_pos);//按照读入时的顺序重新排序
        for(i=1;i<=n;++i) 
            if(!dfn[a[i].pos]) Tarjan(i);//用Tarjan缩点
        for(i=1;i<=n;++i)
            for(j=lnk[i];j;j=e[j].nxt) 
                if(a[i].col^a[e[j].to].col) nadd(a[i].col,a[e[j].to].col),++In[a[e[j].to].col];//更新缩点之后点与点之间的边
        for(i=1;i<=cnt;++i)
            if(!In[i]) dfs(i),ans=max(ans,vist[i]+sum[i]);//贪心的思想,从入度为0的点出发肯定能得到最优答案
        return write(ans),0;
    }
    
  • 相关阅读:
    .NET Core WEB API使用Swagger生成在线接口文档
    .NET Core WEB API中接口参数的模型绑定的理解
    .Net Core使用视图组件(ViewComponent)封装表单文本框控件
    在有主分支和个人分支情况下的TFS使用方法
    SQL Server Profiler (SQl跟踪器)的简单使用
    C# 多线程下的单例模式
    C# 单例模式
    .net core MVC接受来自自前端的GET和POST请求方法的区别
    《Windows Phone 8 Development Internals》读书笔记-1-2-2-连载
    《Windows Phone 8 Development Internals》读书笔记-1-2-1-连载
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu2403.html
Copyright © 2020-2023  润新知