CF1335F Robots on a Grid 题解

题目链接

看到很多题解都是写的 O ⁡ ( n m ⋅ log ⁡ n m ) \operatorname{O}(nm\cdot \log nm) O(nmlognm) 的倍增,这里将一种 O ( n m ) O(nm) O(nm) 的做法

首先,我们可以将给出的网格图转化为若干棵形如下图的内向基环树
为了方便理解,我们将环上的节点从 0 0 0 开始依次编号。满足对于环上的每一条边 [ x → y ] [x \rightarrow y] [xy] x + 1 = y x+1=y x+1=y

image.png
接着,我们让环上点的编号每秒钟沿着环上的边移动一次

image.png

这样,对于所有点,我们都可以求出来他最后到达的环上的点的编号,从而计算此基环树上有多少个机器人可以放在黑色点上
举个栗子:对于一个在点 x x x 上的机器人,设他到环上的最短距离为 c u r cur cur,环上点的个数为 v a l val val,离他最近的环上的点的编号为 p o s pos pos。那么,它在 c u r cur cur 秒后遇到的环上点的编号即为 ( p o s − c u r ) m o d    v a l (pos-cur) \mod val (poscur)modval注意负数的取模!


上面声明了一个较复杂的概念,下面是做法

首先分别建出正向和反向图。
f l a g [ i ] = 0 / 1 flag[i]=0/1 flag[i]=0/1 表示是否存在一个从黑色点出发的机器人,满足可以到达环上的编号为 i i i 的点。 s u m = ∑ f l a g [ i ] sum = \sum flag[i] sum=flag[i] 即为最多的从黑色点出发的机器人

接着,使用 dfs 寻找出每个基环树中的环。找到后,以环为起点开始反向遍历,求出 f l a g flag flag 数组并累及答案( f l a g flag flag 每次找到一棵基环树后都要清空)

最后,可放置的最多的机器人数就是所有基环树的环的大小之和。最多的黑色点出发的机器人数就是 ∑ s u m \sum sum sum

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
const int Maxn=1000000+10,Maxm=1010;
const int dx[]={
    
    1,-1,0,0},dy[]={
    
    0,0,-1,1};
int head[Maxn],nxt[Maxn],to[Maxn];
bool vis[Maxn],a[Maxn];
bool flag[Maxn<<1];
int id[Maxn],d[Maxn];
int n,m,tot,val,ans;
int pos,w,e;
int dfn[Maxn],cnt,edgecnt=1;
inline int get(int x,int y)
{
    
    
	return (x-1)*m+y;
}
inline void add(int x,int y)
{
    
    
	++edgecnt;
	nxt[edgecnt]=head[x];
	to[edgecnt]=y;
	head[x]=edgecnt;
}
inline int read()
{
    
    
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
    
    if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0' && ch<='9')s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
	return s*w;
}
void rev(int x,int cur) // 反向遍历
{
    
    
	vis[x]=1;
	if(a[x])flag[((pos-cur)%val+val)%val]=1; // 注意负数的取模
	for(int i=head[x];i;i=nxt[i])
	{
    
    
		int y=to[i];
		if(id[y]>=w && id[y]<=e)continue; // 不要重复遍历环上的点
		rev(y,cur+1);
	}
}
void init()
{
    
    
	ans=tot=cnt=0,edgecnt=1;
	for(int i=0;i<=n*m+5;++i)
	{
    
    
		head[i]=0;
		flag[i]=vis[i]=a[i]=d[i]=0;
		id[i]=-1;
	}
}
bool dfs(int x,int y)
{
    
    
	id[get(x,y)]=cnt;
	vis[get(x,y)]=1;
	dfn[cnt++]=get(x,y); // 模拟栈
	int u=x+dx[d[get(x,y)]],v=y+dy[d[get(x,y)]];
	if(id[get(u,v)]!=-1)
	{
    
    
		int l=id[get(u,v)],r=id[get(x,y)];
		val=(r-l+1); // 环上的点数
		for(int i=l;i<=r;++i)
		{
    
    
			pos=i-l,w=l,e=r;
			rev(dfn[i],0);
		}
		id[get(x,y)]=-1,--cnt;
		return 1;
	}
	bool tmp=dfs(u,v);
	id[get(x,y)]=-1,--cnt;
	return tmp;
}
int main()
{
    
    
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int T=read();
	while(T--)
	{
    
    
		n=read(),m=read();
		init();
		for(int i=1;i<=n;++i)
		{
    
    
			char s[Maxn];
			scanf("%s",s+1);
			for(int j=1;j<=m;++j)
			if(s[j]=='0')a[get(i,j)]=1;
		}
		for(int i=1;i<=n;++i)
		{
    
    
			char s[Maxn];
			scanf("%s",s+1);
			for(int j=1;j<=m;++j)
			{
    
    
				if(s[j]=='U')d[get(i,j)]=1;
				if(s[j]=='D')d[get(i,j)]=0;
				if(s[j]=='L')d[get(i,j)]=2;
				if(s[j]=='R')d[get(i,j)]=3;
				int k=d[get(i,j)];
				int u=i+dx[k],v=j+dy[k];
				int x=get(i,j),y=get(u,v);
				add(y,x);
			}
		}
		for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
		{
    
    
			if(vis[get(i,j)])continue;
			if(!dfs(i,j))continue;
			ans+=val;
			for(int i=0;i<val;++i)
			{
    
    
				if(flag[i])++tot;
				flag[i]=0;
			}
		}
		printf("%d %d\n",ans,tot);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Brian_Pan_/article/details/107228765