转载链接: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;
}
自己的一些理解,第二种方法确实简单很多,我觉得这种题目一个移动到其他位置,然后其他位置又要移动到另一个位置则要想到倍增。