usaco第二章我的所有题解和程序(四) usaco 2.4
http://hi.baidu.com/mfs666/blog/item/a2a9d443a166aa1573f05d0b.html
2.4是最短路问题
usaco 2.4.1 The Tamworth Two (ttwo)
在一个地图上进行模拟。关键是弄清楚什么情况下就可以认为一定不会相遇。其实很简单,对一个时间点上,如果出现了之前出现过的状态,那么便会进入循环(因为根据规则,相同的状态也会固定产生相应的状态,一一映射,这点思想以后要注意,避重或者判断循环),状态就是牛和人的位置和方向四个元素的组合。状态数为10*10*4*10*10*4=160000,也就是说最多模拟到160000步,如果还没有相遇,下面一步就会出现重复,进入循环。
code:
{
ID: mfs.dev2
PROG: ttwo
LANG: PASCAL
}
program ttwo;
var
m:array[0..11,0..11] of boolean;
s:array[1..2,1..3] of integer;
r,i,j:longint;
l:string;
function ch(w,e:integer):boolean;
begin
ch:=true;
if (w<1) or (e<1) or (w>10) or (e>10) then
ch:=false;
end;
procedure go(a:longint);
var
f,x,y:integer;
begin
case s[a,3] of
1:begin
f:=2;
y:=s[a,2]-1;
x:=s[a,1];
end;
2:begin
f:=3;
y:=s[a,2];
x:=s[a,1]+1;
end;
3:begin
f:=4;
x:=s[a,1];
y:=s[a,2]+1;
end;
4:begin
f:=1;
x:=s[a,1]-1;
y:=s[a,2];
end;
end;
if m[x,y] and ch(x,y) then begin
s[a,1]:=x;s[a,2]:=y;
end else
s[a,3]:=f;
end;
begin
assign(input,'ttwo.in');
assign(output,'ttwo.out');
reset(input);
rewrite(output);
fillchar(m,sizeof(m),0);
r:=0;
for i:=1 to 10 do begin
readln(l);
for j:=1 to 10 do
case l[j] of
'.':m[j,i]:=true;
'C':begin m[j,i]:=true; s[1,1]:=j; s[1,2]:=i; s[1,3]:=1; end;
'F':begin m[j,i]:=true; s[2,1]:=j; s[2,2]:=i; s[2,3]:=1; end;
end;
end;
repeat
inc(r);
for i:=1 to 2 do
go(i);
until ((s[1,1]=s[2,1]) and (s[1,2]=s[2,2])) or (r>160000);
if (s[1,1]=s[2,1]) and (s[1,2]=s[2,2]) then
writeln(r)
else
writeln(0);
close(output);
end.
usaco 2.4.2 Overfencing (maze1)
很WS的题目。对给出的地图从两个出口各做一次floodfill,最好用广搜,但是深搜也很快。每次到达一个格子,如果当前距出口的距离大于已经记录的,那么不再扩展,否则刷新记录并再次扩展。由于地图完全连同,这样一定能求到最短距离,并且算法是有限的。地图的读入很罗嗦,要小心写。我一开始傻到试图用记忆化的dfs来进行floodfill,但是发现不是什么东西都能记忆化的,比如这个并不是阶段图,最短的路径可能要向回头路的方向扩展,最后乱了,参考了一下题解,发现原来这么简单。。。貌似这个题呼悠了好多人,出口只有两个,格子却有3800个,肯定是从出口灌水。。。思想问题,要注意。
不过有个菜问题没想明白,既然能这样,为什么不用floodfill求单源最短路?这样不是很快么,貌似想错了。。。等有空问问。
code:
{
ID: mfs.dev2
PROG: maze1
LANG: PASCAL
}
program maze1;
var
m:array[0..39,0..101] of longint;
e:array[0..39,0..101] of record z,y,s,x:boolean; end;
mc:array[0..3] of record x,y:integer; end;
i,j,w,h,n,l,max,f,c:longint;
k:char;
s:string;
procedure dfs(a,b:integer);
var
t,ma,ff:longint;
begin
if m[a,b]<=f then
exit;
m[a,b]:=f;
if e[a,b].z then begin
inc(f);
dfs(a-1,b);
dec(f);
end;
if e[a,b].y then begin
inc(f);
dfs(a+1,b);
dec(f);
end;
if e[a,b].s then begin
inc(f);
dfs(a,b-1);
dec(f);
end;
if e[a,b].x then begin
inc(f);
dfs(a,b+1);
dec(f);
end;
end;
begin
assign(input,'maze1.in');
assign(output,'maze1.out');
reset(input);
rewrite(output);
readln(w,h);
l:=2*w+1;
for i:=1 to l do begin
read(k);
if k=' ' then begin
inc(c);
mc[c].x:=i div 2;
mc[c].y:=1;
end;
end;
readln;
for i:=1 to 2*h-1 do begin
readln(s);
if (i mod 2)=0 then
begin
for j:=1 to l do
if s[j]=' ' then begin
e[j div 2,i div 2].x:=true;
e[j div 2,(i div 2)+1].s:=true;
end;
continue;
end;
for j:=2 to l-1 do
if (j mod 2)<>0 then
if s[j]=' ' then begin
e[j div 2,(i div 2)+1].y:=true;
e[(j div 2)+1,(i div 2)+1].z:=true;
end;
if s[1]=' ' then begin
inc(c);
mc[c].x:=1;
mc[c].y:=(i div 2)+1;
end;
if s[l]=' ' then begin
inc(c);
mc[c].x:=w;
mc[c].y:=(i div 2)+1;
end;
end;
for i:=1 to l do begin
read(k);
if k=' ' then begin
inc(c);
mc[c].x:=i div 2;
mc[c].y:=h;
end;
end;
for i:=1 to w do
for j:=1 to h do
m[i,j]:=77777777;
if (mc[1].x=mc[2].x) and (mc[1].y=mc[2].y) then
dec(c);
for i:=1 to c do begin
f:=1;
dfs(mc[i].x,mc[i].y);
end;
for i:=1 to w do
for j:=1 to h do
if m[i,j]>max then
max:=m[i,j];
writeln(max);
close(output);
end.
usaco 2.4.3 Cow Tours (cowtour)
一开始理解错题意了,要好好读题。
这个题我是floodfill加分置的floyd加枚举,时间复杂度没问题。首先floodfill统计牧区并记录,然后分别对每个牧区中的牧场进行floyd,过会要用。然后求出每个牧场在本牧区中的最远的一个距离,再在这些距离中求出本牧区的直径,然后枚举每对牧区中的每对牧场,连接起来,这样的新牧区的直径有三种情况,直径在牧区A中,直径在牧区B中,直径在牧区A和B之间,第一二种情况即刚才求的牧区直径,第三种情况,两点间的连接是必经之路,那么直径就是两点距离+A中连点的最远距离+B中连点的最远距离,把这三种情况比较取最大的即新直径(整个的依据就是直径的定义),然后比较输出最小的那种连接方法。最后的分情况讨论的思想以后要注意,可以减少不必要的工作。
另外我做这个题才知道输出浮点数保留位数是writeln(a:0:6);这样子。
{
ID: mfs.dev2
PROG: cowtour
LANG: PASCAL
}
program cowtour;
var
m:array[1..151,1..151] of real;
mf:array[1..151] of boolean;
mm:array[1..151] of real;
ma:array[1..151,1..151] of boolean;
mz:array[1..151] of record x,y:longint; end;
mq:array[1..151] of record l:real; n:integer;d:array[1..151] of integer; end;
i,j,o,c,f,n,h:longint;
t,min:real;
k:char;
procedure ff(a:longint);
var
i:integer;
begin
if mf[a] then
exit;
mf[a]:=true;
if f=0 then begin
inc(c);
f:=1;
end;
inc(mq[c].n);
mq[c].d[mq[c].n]:=a;
for i:=1 to n do
if ma[a,i] then
ff(i);
end;
function pp(a,b:longint):real;
begin
pp:=sqrt((mz[a].x-mz[b].x)*(mz[a].x-mz[b].x)+(mz[a].y-mz[b].y)*(mz[a].y-mz[b].y))
end;
function max(a,b,c:real):real;
var
t:real;
begin
if a>b then
t:=a
else
t:=b;
if t>c then
max:=t
else
max:=c;
end;
begin
assign(input,'cowtour.in');
assign(output,'cowtour.out');
reset(input);rewrite(output);
readln(n);
for i:=1 to n do
for j:=1 to n do
m[i,j]:=999999999;
for i:=1 to n do
readln(mz[i].x,mz[i].y);
for i:=1 to n do begin
for j:=1 to n do begin
read(k);
if k='1' then begin
ma[i,j]:=true;
if m[j,i]<>999999999 then
m[i,j]:=m[j,i]
else
m[i,j]:=pp(i,j);
end;
end;
readln;
end;
for j:=1 to n do begin
f:=0;
ff(j);
end;
for j:=1 to n do
m[j,j]:=0;
min:=99999999;
for h:=1 to c do
for o:=1 to mq[h].n do
for i:=1 to mq[h].n do
for j:=1 to mq[h].n do begin
t:=m[mq[h].d[i],mq[h].d[o]]+m[mq[h].d[o],mq[h].d[j]];
if t<m[mq[h].d[i],mq[h].d[j]] then
m[mq[h].d[i],mq[h].d[j]]:=t;
end;
for h:=1 to c do
for i:=1 to mq[h].n do begin
for j:=1 to mq[h].n do
if m[mq[h].d[i],mq[h].d[j]]>mm[mq[h].d[i]] then
mm[mq[h].d[i]]:= m[mq[h].d[i],mq[h].d[j]];
if mm[mq[h].d[i]]>mq[h].l then
mq[h].l:=mm[mq[h].d[i]];
end;
for h:=1 to c do
for o:=h+1 to c do
for i:=1 to mq[h].n do
for j:=1 to mq[o].n do begin
t:=max(mq[h].l,mq[o].l,mm[mq[h].d[i]]+mm[mq[o].d[j]]+pp(mq[h].d[i],mq[o].d[j]));
if t<min then
min:=t;
end;
{for i:=1 to n do begin
for j:=1 to n do
write(m[i,j]:0:2,' ');
writeln;
end;
for i:=1 to n do
writeln(mm[i]:0:2);
}writeln(min:0:6);
close(output);
end.
usaco 2.4.4 Bessie Come Home (comehome)
这个是标准的带权单源最短路。n为52,完全可以用floyd减少编码复杂度。要注意的是好好读题,没对农场间可能有多条边,要在预处理时选取最小边。
code:
{
ID: mfs.dev2
PROG: comehome
LANG: PASCAL
}
program comehome;
var
m:array[1..52,1..52] of longint;
f:array['A'..'Z'] of boolean;
i,j,o,p,max:longint;
a,b,t,tt:char;
function gn(x:char):longint;
begin
gn:=ord(x)-70;
if x in ['A'..'Z'] then
gn:=ord(x)-64;
end;
begin
assign(input,'comehome.in');
assign(output,'comehome.out');
reset(input);rewrite(output);
readln(p);
for i:=1 to 52 do
for j:=1 to 52 do begin
m[i,j]:=1073741822;
end;
for i:=1 to p do begin
readln(a,t,b,tt,j);
if a in ['A'..'Z'] then
f[a]:=true;
if b in ['A'..'Z'] then
f[b]:=true;
if j<m[gn(a),gn(b)] then begin
m[gn(a),gn(b)]:=j;
m[gn(b),gn(a)]:=j;
end;
end;
for i:=1 to 52 do
for j:=1 to 52 do
for o:=1 to 52 do begin
max:=m[j,i]+m[i,o];
if max<m[j,o] then
m[j,o]:=max;
end;
b:='Z';
max:=2147483646;
for a:='A' to 'Y' do
if f[a] then
if m[gn(a),26]<max then begin
max:=m[gn(a),26];
b:=a;
end;
writeln(b,' ',max);
close(output);
end.
usaco 2.4.5 Fractions to Decimals (fracdec)
这个题儿一开始竟然不知道怎么做,为什么呢?因为我平时日常学习时的计算除了考试都使计算器,根本就不记得怎么列竖式(也就是题解所说的长除)计算除法了,所以没看出来。。。其实很简单,对于长除的每一步的余数,如果出现重复那么就意味着第一次到第二次出现计算的这一部分就是循环结。因为余数这个状态的扩展也是一一对应的,同一个余数必然扩展出相应的同样的下一个余数,依此类推可证明此算法。开hash记录每个余数的出现位置,发现重复就处理输出。这样的话就是o(n)啦,很快的。
顺便说一下把循环小数还原成分数的方法,可以生成一些浮点值有意思的分数,来测试这个题。混循环小数分子是小数点后到第一个循环结结束形成的数字减去非循环部分形成的数字,分母是循环结长度个9后跟非循环部分长度个0形成的数字。
code:
{
ID: mfs.dev2
PROG: fracdec
LANG: PASCAL
}
program fracdec;
var
s,t:ansistring;
m,d,i,j,a,b:longint;
p:array[0..100008] of longint;
begin
assign(input,'fracdec.in');
assign(output,'fracdec.out');
reset(input);
rewrite(output);
readln(a,b);
d:=a div b;
str(d,t);
s:=t;
s:=s+'.';
m:=a mod b;
if m=0 then
s:=s+'0';
if m<>0 then
repeat
p[m]:=length(s);
m:=m*10;
d:=m div b;
m:=m mod b;
str(d,t);
s:=s+t;
if m=0 then
break;
if p[m]<>0 then
s:=copy(s,1,p[m])+'('+copy(s,p[m]+1,length(s)-p[m])+')';
until (m=0) or (p[m]<>0);
for i:=1 to length(s) do
begin
write(s[i]);
if i mod 76 =0 then
writeln;
end;
writeln;
close(output);
end.