Codeforces Round #634 (Div. 3) F. Robots on a Grid题解(拓扑找环+逆向dfs/倍增)

转载链接:https://blog.csdn.net/qq_45458915/article/details/105515708?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.nonecase

题目链接

题目大意

题目大意:给出一个 n * m 的矩阵,矩阵的每一个格子都有一个颜色,颜色非黑即白,除此之外每个格子还有一个指令,分别为:

’ U ':向上一个单位
’ D ':向下一个单位
’ R ':向右一个单位
’ L ':向左一个单位
每个格子都可以放置机器人,对于每个机器人而言,每一秒都会遵循格子上的指令行走,换句话说,机器人会永不停止的行走,现在问,如何放置机器人,可以使得矩阵中机器人的数量最多,且互相永远不会冲突,即任意时刻每个格子里至多有只能一个机器人,在满足矩阵中机器人数量最多的前提下,如何摆放可以使得在黑色格子上的机器人最多,分别输出这两个答案

这个题目两个思路,一种思路比较好想,但是细节较多,码量较大,另一种思路需要一定的思维,实现简单,码量小

思路1

先从第一种思路说起,因为机器人会永不停止的行走,就说明矩阵中一定存在着首尾相接的环,显然所有环的长度之和就是ans1了,也可以知道了ans1与颜色无关,对于ans2我们需要多考虑一点东西:


在上面这个情况中,点1和点2已经组成了一个首尾相接的环路,所以ans1显然为 2 ,就不多赘述了,但是加上颜色的限制,如果点1和点2同为白色,但是点3为黑色,那么初始时最优的摆放策略肯定是摆在点3和点2,因为这样既能满足ans1=2,且ans2=1,这种情况该如何考虑呢?对于环上的任意一个节点开始,沿着反向边进行遍历,跑出 dis 距离数组就行了,显然如果在这样一个整体中,如果最终不想冲突的话,dis[ i ] % len 的位置上至多只能有一个机器人,此处的 len 为首位相接的环路长度

想清楚上面 dis[ i ] % len 这个地方后,实现就好了,重新捋一下这个题的过程,首先整个矩阵可以视为一个 n * m 个结点和 n * m 条边组成的有向图,既然是有向图,就可以拓扑找环,根据拓扑排序的性质将整个图处理到只剩下首尾相接的环路,此后对于每个环路而言,找到上面的任意一个结点开始,反向 dfs 跑出 dis 数组,然后判断就好了

细节较多,代码如下:

代码1

#include<set>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=1e6+5;
char color[maxn],dir[maxn];
int t,n,m,deg[maxn],ans1,ans2,round;
int in[maxn];//正向边
vector<int> out[maxn];//反向边
bool vis[maxn];
vector<int> vec;
set<int> se;
inline int id(int x,int y){
    return (x-1)*m+y;
}
void init(){
    ans1=ans2=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            deg[id(i,j)]=vis[id(i,j)]=in[id(i,j)]=0;
            out[id(i,j)].clear();
        }
    }
}
void add(int u,int v){
    in[u]=v;
    out[v].push_back(u);
    deg[v]++;
}
void getcircle(){//拓扑找环
    queue<int> que;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(deg[id(i,j)]==0){
                que.push(id(i,j));
            }
        }
    }
    while(!que.empty()){
        int x=que.front();que.pop();
        deg[in[x]]--;
        if(deg[in[x]]==0){
            que.push(in[x]);
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            ans1+=(deg[id(i,j)]!=0);
        }
    }
}
void dfs(int pos,int len){//逆向dfs
    if(color[pos]=='0'){
        vec.push_back(len);
    }
    for(int i=0;i<out[pos].size();i++){
        if(vis[out[pos][i]]==0){
            vis[out[pos][i]]=1;
            dfs(out[pos][i],len+1);
        }else{
            round=len+1;//环的大小
        }
    }
}
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&m);
        init();
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                scanf(" %c",&color[id(i,j)]);
            }
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                scanf(" %c",&dir[id(i,j)]);
                if(dir[id(i,j)]=='R'){
                    add(id(i,j),id(i,j+1));
                }else if(dir[id(i,j)]=='L'){
                    add(id(i,j),id(i,j-1));
                }else if(dir[id(i,j)]=='U'){
                    add(id(i,j),id(i-1,j));
                }else if(dir[id(i,j)]=='D'){
                    add(id(i,j),id(i+1,j));
                }
            }
        }
        getcircle();
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                if(deg[id(i,j)]&&!vis[id(i,j)]){
                //在环里面,而且没有走过
                    vec.clear();
                    se.clear();
                    vis[id(i,j)]=1;
                    dfs(id(i,j),0);
                    for(int k=0;k<vec.size();k++){
                        se.insert(vec[k]%round);
                    }
                    ans2+=se.size();
                }
            }
        }
        printf("%d %d\n",ans1,ans2);
    }
    return 0;
}

思路2

下面说一下第二种思路,假如两个机器人会冲突的话,也就是说在某个时刻,某个格子中同时出现了两个机器人,那么在接下来的移动过程中,显然这两个机器人的路径将保持一致,我们可以先假设 n * m 个格子中初始时都有一个机器人,先让这些机器人运动 n * m 秒,甚至更多,因为 n * m 的一个矩阵,最大可以形成的一个首尾相接的环路长度就是 n * m ,所以先让所有机器人都运动 n * m 秒后,该重合的机器人都已经重合了,并且不难统计每个位置中有多少个来自黑格的机器人,此时矩阵中仍然有机器人的地方,就说明这个位置是首尾相接的环,那么ans1++,如果这个地方至少有一个来自黑格的机器人,那就说明来到这个位置的机器人初始时可以摆在黑格上,即ans2++

实现的话肯定不是暴力模拟 n * m 秒,可以利用倍增,首先将 n * m 个格子按照 0 ~ n * m - 1 编号,那么 dp[ i ][ j ] 就代表编号为 i 的点走 2^j 步可以到达的地方,这里类比于树上倍增就好,剩下的实现起来就比较简单了

代码

#include<set>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=1e6+5;
char color[maxn],dir[maxn];
int t,n,m,ans1,ans2;
int dp[maxn][25],flag1[maxn],flag2[maxn];
inline int id(int x,int y){
    return (x-1)*m+y;
}
void init(){
    ans1=ans2=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            flag1[id(i,j)]=flag2[id(i,j)]=0;
        }
    }
}
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&m);
        init();
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                scanf(" %c",&color[id(i,j)]);
            }
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                scanf(" %c",&dir[id(i,j)]);
                if(dir[id(i,j)]=='R'){
                    dp[id(i,j)][0]=id(i,j+1);
                }else if(dir[id(i,j)]=='L'){
                    dp[id(i,j)][0]=id(i,j-1);
                }else if(dir[id(i,j)]=='U'){
                    dp[id(i,j)][0]=id(i-1,j);
                }else if(dir[id(i,j)]=='D'){
                    dp[id(i,j)][0]=id(i+1,j);
                }
            }
        }
        for(int time=1;time<=20;time++){
            for(int i=1;i<=n;i++){
                for(int j=1;j<=m;j++){
                    dp[id(i,j)][time]=dp[dp[id(i,j)][time-1]][time-1];
                }
            }
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                flag1[dp[id(i,j)][20]]++;
                flag2[dp[id(i,j)][20]]+=(color[id(i,j)]-'0'==0);
            }
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                ans1+=(flag1[id(i,j)]>0);
                ans2+=(flag2[id(i,j)]>0);
            }
        }
        printf("%d %d\n",ans1,ans2);
    }
    return 0;
}

自己的一些理解,第二种方法确实简单很多,我觉得这种题目一个移动到其他位置,然后其他位置又要移动到另一个位置则要想到倍增。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值