BAKTERIJE

link & 博客园食用

题意

有一个矩形区域被划分成了许多小方格,这些方格中有一个是陷阱。这个区域内有一些细菌,这些细菌有初始方向,它们会沿着各自的初始方向去到相邻的格子(特别地,假如当前方向会导致它们冲出矩形那么细菌会转身),每到一个新的格子,细菌就会按照格子中的数值旋转一定的角度从而改变自己的方向。显然很有可能这些细菌有一个时刻会进入陷阱中,但是一般来说为了防止打草惊蛇这些陷阱不会发作,进入陷阱的细菌会照常改变方向然后离开。陷阱发挥作用当且仅当所有细菌同时进入陷阱,题目中问的就是陷阱第一次发作的时间。

解法

很显然的是细菌所到达的格子会循环往复。因为细菌的状态由所在位置和当前方向决定,当前状态会产生唯一的一个新状态,而状态是有限的,所以肯定会产生循环。那么就可以推断出每一个细菌的路径一定是分为两个阶段,第一个阶段从初始状态走到一个状态 \(A\) ,第二个阶段就是从 \(A\) 通过一些时间重新回到 \(A\) ,这样循环往复。

既然如此可以分两类情况。对于第一阶段大可以直接枚举时间然后判断是否合法,因为可以想见可能的时间一定不多。对于第二个阶段,假设第一次到达某个状态的时间为 \(t_0\),每次循环的时间是 \(c\),那么每次到达这个点的时间都可以表示为 \(t=t_0+kc(k\in N)\) ,换句话说就是 \(t\equiv t_0\pmod c\) ,注意这里的 \(t\) 应该不小于 \(t_0\) 。每个陷阱会对应四种状态(也就是四种方向),所以所有细菌就可能会有 \(K^4\) 种掉入陷阱的方式。这个数量也不多,可以枚举之后解同余方程组即可。

另外就是理论上这道题要用 __int128 来存储,但造出这样的数据似乎并不是很容易,于是只用龙龙就可以存的下了。

代码有注释,蒟蒻代码能力有限请大佬轻喷。

#include<bits/stdc++.h>
//#define feyn
#define int long long
const int N=60;
using namespace std;
inline void read(int &wh){
	wh=0;int f=1;char w=getchar();
	while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
	while(w>='0'&&w<='9'){wh=wh*10+w-'0';w=getchar();}
	wh*=f;return;
}
inline int min(int s1,int s2){
	return s1<s2?s1:s2;
}

int m,n,num,x,y,data[N][4],cnt[N],st[N];

int c[N][N],vis[N][N][4];
int f[4][2]={{-1,0},{0,1},{1,0},{0,-1}};//方向数组 
inline bool check(int a,int b){
	return a<1||b<1||a>m||b>n;//是否跳出边界 
}
void solve(int wh){
	memset(vis,0,sizeof(vis));
	int a,b,now;read(a);read(b);
	char w[N];scanf("%s",w);
	switch(w[0]){
		case 'U':now=0;break;
		case 'R':now=1;break;
		case 'D':now=2;break;
		case 'L':now=3;break;
	}
	for(int i=1;i<=m;i++){
		scanf("%s",w);
		for(int j=1;j<=n;j++)c[i][j]=w[j-1]-'0';
	}//处理输入 
	
	int an=0;
	while(true){
		an++;
		if(vis[a][b][now]){cnt[wh]=an-vis[a][b][now];st[wh]=vis[a][b][now];return;}
		//如果一个状态已经被访问过了,那么两次访问的时间差就会是循环长度 
		vis[a][b][now]=an;if(a==x&&b==y)data[wh][now]=an;//记录第一次进入陷阱的时间 
		now=(now+c[a][b])%4;//按照题意改变方向 
		if(check(a+f[now][0],b+f[now][1]))now=(now+2)%4;//如果走出矩阵了 
		a+=f[now][0],b+=f[now][1];//移动到下一个格子 
	}
}

inline bool able(int wh,int t){
	for(int i=0;i<4;i++){
		if(data[wh][i]==0)continue;
		if(data[wh][i]<st[wh]){if(data[wh][i]==t)return true;}
		else if(t>=data[wh][i]&&t%cnt[wh]==data[wh][i]%cnt[wh])return true;
	}
	return false;
}

int limit,ans=1e18,a[10],b[10];

struct node{int x,y,g;};
inline node exgcd(int a,int b){
	if(b==0)return (node){1,0,a};
	node an=exgcd(b,a%b);
	return (node){an.y,an.x-a/b*an.y,an.g};
}//扩展欧几里得的板子 
void work(){
	int M=b[1],x0=a[1];
	for(int i=2;i<=num;i++){
		node an=exgcd(M,b[i]);
		int c=a[i]-x0,bg=b[i]/an.g;
		if(c%an.g)return;
		int x=an.x*(c/an.g)%bg;
		x0+=x*M;M*=bg;
		x0=(x0%M+M)%M;
	}//扩展中国剩余定理的板子 
	while(x0<limit)x0+=M;//要保证最后的答案大于等于limit 
	ans=min(ans,x0);return;//更新答案 
}
void dfs(int wh){
	if(wh>num){work();return;}
	for(int i=0;i<4;i++){
		if(data[wh][i]<st[wh])continue;//没进入循环的状态只会出现一次,不能加入同余方程 
		a[wh]=data[wh][i]%cnt[wh];
		b[wh]=cnt[wh];//记录余数和模数 
		dfs(wh+1);
	}
}

signed main(){
	
	#ifdef feyn
	freopen("in.txt","r",stdin);
	#endif
	
	read(m);read(n);read(num);
	read(x);read(y);
	for(int i=1;i<=num;i++)solve(i);
	for(int i=1;i<=num;i++){
		bool ok=true;
		for(int j=0;j<4;j++){
			limit=max(limit,st[i]);
			if(data[i][j])ok=false;
		}
		if(ok){printf("-1");return 0;}//如果有细菌根本到不了陷阱 
	}
	//limit是所有细菌中进入循环的最晚细菌的时间 
	
	//处理第一阶段的答案,枚举加验证 
	for(int i=0;i<=limit;i++){
		bool ok=true;
		for(int j=1;j<=num;j++){
			if(able(j,i)==false){ok=false;break;}//如果有细菌到不了 
		}
		if(ok){printf("%lld",i);return 0;}//假如存在小于limit的答案直接输出 
	}
	
	dfs(1);//枚举状态 
	printf("%lld",ans);
	
	return 0;
}
posted @ 2022-07-14 08:52  Feyn618  阅读(31)  评论(0编辑  收藏  举报