USACO-Section2.4 The Tamworth Two【模拟法】

题目描述:

两只牛逃跑到了森林里。农夫John开始用他的专家技术追捕这两头牛。你的任务是模拟他们的行为(牛和John)。
追击在10x10的平面网格内进行。一个格子可以是:
一个障碍物, 两头牛(它们总在一起), 或者 农民John. 两头牛和农民John可以在同一个格子内(当他们相遇时),但是他们都不能进入有障碍的格子。
一个格子可以是:
. 空地
* 障碍物
C 两头牛
F 农民John
这里有一个地图的例子:

*...*.....
......*...
...*...*..
..........
...*.F....
*.....*...
...*......
..C......*
...*.*....
.*.*......

牛在地图里以固定的方式游荡。每分钟,它们可以向前移动或是转弯。如果前方无障碍(地图边沿也是障碍),它们会按照原来的方向前进一步。否则它们会用这一分钟顺时针转90度。 同时,它们不会离开地图。
农民John深知牛的移动方法,他也这么移动。
每次(每分钟)农民John和两头牛的移动是同时的。如果他们在移动的时候穿过对方,但是没有在同一格相遇,我们不认为他们相遇了。当他们在某分钟末在某格子相遇,那么追捕结束。
读入十行表示农夫John,两头牛和所有障碍的位置的地图。每行都只包含10个字符,表示的含义和上面所说的相同,你可以确定地图中只有一个’F’和一个’C’.’F’和’C’一开始不会处于同一个格子中。
计算农夫John需要多少分钟来抓住他的牛,假设牛和农夫John一开始的行动方向都是正北(即上)。 如果John和牛永远不会相遇,输出0。

INPUT FORMAT

第1-10行:
每行10个字符,表示如上文描述的地图。


SAMPLE INPUT

*...*.....
......*...
...*...*..
..........
...*.F....
*.....*...
...*......
..C......*
...*.*....
.*.*......

OUTPUT FORMAT

输出一个数字,表示John需要多少时间才能抓住牛们。如果John无法抓住牛,则输出0。


SAMPLE OUTPUT

49


解题思路:

看到这一题的第一反应就是模拟,当农民和奶牛与之前的某一次状态相同时停止,但是这个明显是最容易想到的复杂度极高的方法,所以又手动模拟了一下,发现农民和奶牛一定有周期。然后决定通过数学的方法计算结果。先模拟一遍农民的走法,当出现循环时停止,然后模拟奶牛的走法。我们假设农民的周期为a,奶牛的周期为b,奶牛在第一次循环时步数为b1的时候到了农民a1时走过的位置。如果a1==b1,显然是结果。如果不相同,先判断a1时农民是否是循环阶段,这个很重要,我第一次时没注意,第9个点黑了。有公式:ax+a1=bx+b1=ans。则ax-by=b1-a1,如果a和b互质,则一定有解,否则当b1-a1为他们最大公约数的倍数时有解。

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#define INF 2147483647
int maze[12][12];//地图 
int fway[12][12][4],cway[12][12][4];//用于记录以一方向再次到达此点时的步数 
int fcount=1,ccount=1;//记录步数
int fTras=0,cTras=0,fTstart=0,cTstart=0;//记录周期及周期起始步数(cTstart多余) 
int f[3],c[3];//01用来记位置,2用来记方向 
int direction[4][2]={{-1,0},{0,1},{1,0},{0,-1}};//分别为北,东,南,西 
int ans=INF;//记录答案 
//这一题用暴搜也应该不会超时,由于一定有周期,所以循环两个周期的最小公倍数次也可以解,我为了降低复杂度,将后面循环的时间用数学公式计算了 

int min(int a,int b){//返回最小值 
    return a<b?a:b;
}
int gcd(int a,int b){//求最大公约数 
    return b?gcd(b,a%b):a;
}
void print(){//用于调用观察,没有写在main函数里 
    int i,j;
    for(i=0;i<12;i++){//输出地图 
    for(j=0;j<11;j++)
        printf("%d ",maze[i][j]);
        printf("%d\n",maze[i][j]);
    }
    printf("\n");
    for(i=0;i<12;i++){//输出奶牛向北的行径路线 
        for(j=0;j<11;j++)
            printf("%d\t",cway[i][j][0]);
            printf("%d\n",cway[i][j][0]);
    }
    printf("\n");
    for(i=0;i<12;i++){//输出奶牛向东的行径路线,以此类推 
        for(j=0;j<11;j++)
            printf("%d\t",cway[i][j][1]);
            printf("%d\n",cway[i][j][1]);
    }
    printf("\n");
    for(i=0;i<12;i++){
        for(j=0;j<11;j++)
            printf("%d\t",cway[i][j][2]);
            printf("%d\n",cway[i][j][2]);
    }
    printf("\n");
    for(i=0;i<12;i++){
        for(j=0;j<11;j++)
            printf("%d\t",cway[i][j][3]);
            printf("%d\n",cway[i][j][3]);
    }
    printf("\n");
    for(i=0;i<12;i++){
        for(j=0;j<11;j++)
            printf("%d\t",fway[i][j][0]);
            printf("%d\n",fway[i][j][0]);
    }
    printf("\n");
    for(i=0;i<12;i++){
        for(j=0;j<11;j++)
            printf("%d\t",fway[i][j][1]);
            printf("%d\n",fway[i][j][1]);
    }
    printf("\n");
    for(i=0;i<12;i++){
        for(j=0;j<11;j++)
            printf("%d\t",fway[i][j][2]);
            printf("%d\n",fway[i][j][2]);
    }
    printf("\n");
    for(i=0;i<12;i++){
        for(j=0;j<11;j++)
            printf("%d\t",fway[i][j][3]);
            printf("%d\n",fway[i][j][3]);
    }
}
void fbfs(){//farmer行进路线,循环时停止 
    while(1){
        fcount++;
        if(maze[f[0]+direction[f[2]][0]][f[1]+direction[f[2]][1]]==0){//识别进行下一步时f的状态,判断是否撞墙 
            f[2]=(f[2]+1)%4;
        }else{
            f[0]=f[0]+direction[f[2]][0];
            f[1]=f[1]+direction[f[2]][1];
        }
        if(fway[f[0]][f[1]][f[2]]){
            fTras=fcount-fway[f[0]][f[1]][f[2]];
            fTstart=fway[f[0]][f[1]][f[2]];
            break;
        }else{
            fway[f[0]][f[1]][f[2]]=fcount;
        }
    }
}
void cbfs(){//cow行进路线,循环时停止 
    int flag=0;//判断是否在c做循环时相遇 
    while(1){
        ccount++;
        if(flag) break; 
        if(maze[c[0]+direction[c[2]][0]][c[1]+direction[c[2]][1]]==0){//识别进行下一步时c的状态 
            c[2]=(c[2]+1)%4;
        }else{
            c[0]=c[0]+direction[c[2]][0];
            c[1]=c[1]+direction[c[2]][1];
        }
        if(cway[c[0]][c[1]][c[2]]){
            cTras=ccount-cway[c[0]][c[1]][c[2]];
            cTstart=cway[f[0]][f[1]][f[2]];
            break;
        }else{
            cway[c[0]][c[1]][c[2]]=ccount;
        }
        for(int i=0;i<4;i++)//如果c做循环时遇到结果了,直接返回 
        if(fway[c[0]][c[1]][i]==ccount){
            ans=ccount;
            flag=1;
            break;
        }
    }
}
int count(int a,int b,int a1,int b1){//ax+a1=by+b1=k,所以ax-by=b1-a1
    int temp,c;
    if(a1>b1){
        temp=a;a=b;b=temp;
        temp=a1;a1=b1;b1=temp;
    }
    c=b1-a1;
    if(gcd(fTras,cTras)>1){//如果两个周期值的最大公约数不为1,只有当b1-a1为gcd(a,b)倍数时才有解 
        if(c%gcd(fTras,cTras)!=0)return INF;
    }
    int atemp=c/a,btemp=0;
    while(1){//我用了比较笨的方法来算何时相等 = = 
    if(a*atemp-b*btemp==c)return a*atemp+a1;
    if(a*atemp-b*btemp<c){
        atemp++;
    }
    if(a*atemp-b*btemp>c){
        btemp++;
    }
    } 
}
void countmin(){//搜索两者在同一格的步数并计算,注意要在f循环后 
    for(int i=1;i<11;i++){
        for(int j=1;j<11;j++){
            for(int k=0;k<4;k++){
                for(int l=0;l<4;l++){
                    if(fway[i][j][k]&&cway[i][j][l]&&fway[i][j][k]>fTstart){
                        ans=min(ans,count(fTras,cTras,fway[i][j][k],cway[i][j][l]));
                    }
                }
            }
        }
    }
}
int main(){
    FILE *fin  = fopen ("ttwo.in", "r");
    FILE *fout = fopen ("ttwo.out", "w");
    int i,j;
    for(i=1;i<=10;i++){
        for(j=1;j<=10;j++){
            char a;
            fscanf(fin,"%c",&a);
            if(a=='.'||a=='F'||a=='C') maze[i][j]=1;
            if(a=='F') f[0]=i,f[1]=j,fway[i][j][0]=1;
            if(a=='C') c[0]=i,c[1]=j,cway[i][j][0]=1;
        }
        fscanf(fin,"\n");
    }
    fbfs();
    cbfs();
    printf("%d %d\n",fTras,cTras);
    if(ans!=INF)fprintf(fout,"%d\n",ans-1);
    else{
    countmin();
    if(ans==INF)fprintf(fout,"%d\n",0);
    else fprintf(fout,"%d\n",ans-1);
    }
    exit(0);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值