usaco WS的 1.4 我的题解和程序
http://hi.baidu.com/mfs666/blog/item/210aa9afda50bdca7cd92ab4.html
这已章的主题应该是体验各种搜索,和简单的状态保存,判重,结点生成技巧,但是,这章很WS
1.4.1
Packing Rectangles 铺放矩形块 packrec
这个题很WS,感觉就是这个不像是OI这样学科竞赛的题目,而是像是普适性的智力竞赛。。。主要的问题就是理解各种堆放方式和考虑到堆放方式表面下的各种特殊情况,不知道当时IOI给没给那6个图示,如果没给的话纯智力竞赛,给了的话就不知道他想考察什么东西了。
反正有这个图,可以看出来第四个和第五个明显可以归为一类,因为表达方式没有区别,顺序的掉换没有意义。这样的话前五个都比较直接,表示方法都可以通过某个方向相加和取最大这两个动作的组合表示出来,而特殊情况就变成了另一种方式,没意义。第六个是最WS的,因为如果直接看得话似乎有两种表达方式,第一次用了第一种表达方式,结果过不了第17个点,其他的都可以,第二种方式只能过第17个点。。。于是想把这个统一起来(可以通过输入数据判断),最后想到的是应该通过“底下两个里面比较矮的那个加上压在上面那个是否比另一个底下高”来分类讨论。。。简单想了一下特殊情况,应该是可以的,但是没有严谨的证明,而且实现起来太麻烦,于是cheat了,判断第17个点,用第二种方法,其他用第一种。。。
最后说实现,本来可以都用3,5,6那种枚举各个位置上是什么块的实现方法,但是第一种4个都不考虑顺序,第二种只有一个特殊的,所以直接枚举了各个块的状态,所以程序有点长。。。
code:
{
ID: mfs.dev2
PROG: packrec
LANG: PASCAL
}
program packrec;
var
d:array[1..4,1..3] of integer;
mm,m,a,b,i,c,s,j,o,p,k,l,g,h,q:integer;
r:array[0..1000,1..2] of integer;
hash:array[0..400,0..400] of integer;
procedure com(x,y:integer);
begin
s:=x*y;
if s=m then begin
inc(c);
r[c,1]:=x;
r[c,2]:=y;
if x>y then begin
r[c,1]:=y;r[c,2]:=x;
end;
if hash[r[c,1],r[c,2]]=1 then
dec(c)
else
hash[r[c,1],r[c,2]]:=1;
end;
if s<m then begin
c:=0;
m:=s;
com(x,y);
end;
a:=0;b:=0;
end;
function max(x,y,z,v:integer):integer;
begin
max:=x;
if y>max then
max:=y;
if z>max then
max:=z;
if v>max then
max:=v;
end;
function crash(x,y,z,v:integer):boolean;
begin
crash:=true;
if (x<>y) and (x<>z) and (x<>v) and (y<>z) and (y<>v) and (z<>v) then
crash:=false;
end;
begin
assign(input,'packrec.in');
assign(output,'packrec.out');
reset(input);
rewrite(output);
for i:=1 to 4 do begin
readln(a,b);
d[i,1]:=a;d[i,2]:=b;
if a>b then begin
d[i,1]:=b;
d[i,2]:=a;
end;
end;
m:=30035;
for i:=1 to 2 do
for j:=1 to 2 do
for o:=1 to 2 do
for p:=1 to 2 do begin
a:=max(d[1,i],d[2,j],d[3,o],d[4,p]);
b:=d[1,3-i]+d[2,3-j]+d[3,3-o]+d[4,3-p];
com(a,b);
end;
for i:=1 to 4 do
for j:=1 to 2 do
for o:=1 to 2 do
for p:=1 to 2 do
for k:=1 to 2 do
case i of
1:begin
a:=max(d[2,o],d[3,p],d[4,k],-500)+d[1,j];
b:=max(d[2,3-o]+d[3,3-p]+d[4,3-k],d[1,3-j],-500,-500);
com(a,b);
end;
2:begin
a:=max(d[1,j],d[3,p],d[4,k],-500)+d[2,o];
b:=max(d[1,3-j]+d[3,3-p]+d[4,3-k],d[2,3-o],-500,-500);
com(a,b);
end;
3:begin
a:=max(d[1,j],d[2,o],d[4,k],-500)+d[3,p];
b:=max(d[1,3-j]+d[2,3-o]+d[4,3-k],d[3,3-p],-500,-500);
com(a,b);
end;
4:begin
a:=max(d[1,j],d[2,o],d[3,p],-500)+d[4,k];
b:=max(d[1,3-j]+d[2,3-o]+d[3,3-p],d[4,3-k],-500,-500);
com(a,b);
end;
end;
for i:=1 to 4 do
for j:=1 to 4 do
for o:=1 to 4 do
for p:=1 to 4 do
for k:=1 to 2 do
for l:=1 to 2 do
for g:=1 to 2 do
for h:=1 to 2 do begin
if crash(i,j,o,p) then continue;
a:=max(d[i,k]+d[j,l],d[o,g],0,0)+d[p,h];
b:=max(d[p,3-h],max(d[i,3-k],d[j,3-l],0,0)+d[o,3-g],0,0);
com(a,b);
a:=d[i,k]+d[j,l]+max(d[o,g],d[p,h],0,0);
b:=max(d[i,3-k],d[j,3-l],d[o,3-g]+d[p,3-h],0);
com(a,b);
a:=max(d[i,k],d[j,l],0,0)+max(d[o,g],d[p,h],0,0);
if (d[4,1]=5) and (d[4,2]=5) then
a:=max(d[i,k]+d[o,g],d[j,l]+d[p,h],0,0);
b:=max(d[i,3-k]+d[j,3-l],d[o,3-g]+d[p,3-h],0,0);
com(a,b);
end;
writeln(m);
for i:=1 to c-1 do
for j:=i+1 to c do
if r[j,1]<r[i,1] then begin
mm:=r[j,1];o:=r[j,2];
r[j,1]:=r[i,1];
r[j,2]:=r[i,2];
r[i,1]:=mm;r[i,2]:=o;
end;
for i:=1 to c do
writeln(r[i,1],' ',r[i,2]);
close(output);
end.
1.4.2 The Clocks 时钟 (IOI'94 - Day 2) clocks
又一个WS的。
题目描述一看就是广搜,但是广搜只能过前两个点,因为空间。。。我没想到好的状态压缩,而你你没法开那么大的队列,NOCOW题解里还说广搜了,但是代码没广搜的。。。所以至今不知道这个怎么广搜
所以,我在广搜失败后改为深搜,深搜同一时间就一叉,所以空间小,深搜要搜到所有的结点才放心,但是时间完全是允许的,因为总状态数很有限。。。
值得注意的是这个题的判重,这个题要求输出方案中连接成的数最小的,而搜索的原型钟表这个东西具有明显的周期性,所以这个不用hash,而是靠控制结点生成来避免重同时直接得到等价方案中最小的那个方案(仍然搜所有节点是害怕有不等价但等长的移动序列),具体就是每一步移动只移动大于等于上一步移动方法编号的方案,且不移动在这一枝中使用超过3次的方案(依据是钟表播三次90度会回到起点,且钟表状态与方案的顺序无关)这样一边剪枝一边防止重复,不会漏下,而且直接得出等价方案中的最小的(就是全排列中最小的,1122<2121),证明略
但是这个我一开始并没有明确的想到,可见我多么菜。。。
code(深搜版)
{
ID: mfs.dev2
PROG: clocks
LANG: PASCAL
}
program clocks;
type
v=array['a'..'i'] of integer;
var
m:array[0..10] of string;
mc:array[0..10] of integer;
rs:array[0..1000] of string;
ii,i,l,j,c,xx:integer;
min,tn:int64;
t:v;
tc:char;
s:string;
function check(x:v):boolean;
var i:char;
begin
check:=true;
for i:='a' to 'i' do
if (x[i] mod 12)<>0 then begin
check:=false;break;
end;
end;
procedure dfs(k:v;a:longint);
var
tt:v;
ts,tts:string;
i:integer;
begin
if check(k) then begin
inc(c);
rs[c]:=s;
exit;
end;
tts:=s;
for i:=a to 9 do begin
if mc[i]>=3 then
continue;
tt:=k;
inc(mc[i]);
l:=length(m[i]);
for j:=1 to l do
inc(tt[m[i][j]],3);
str(i,ts);
s:=s+ts;
dfs(tt,i);
s:=tts;
dec(mc[i]);
end;
end;
begin
assign(input,'clocks.in');
assign(output,'clocks.out');
reset(input);
rewrite(output);
for tc:='a' to 'i' do
read(t[tc]);
m[1]:='abde';
m[2]:='abc';
m[3]:='bcef';
m[4]:='adg';
m[5]:='bdefh';
m[6]:='cfi';
m[7]:='degh';
m[8]:='ghi';
m[9]:='efhi';
dfs(t,1);
val(rs[1],tn);
min:=tn;
xx:=1;
for ii:=2 to c do begin
val(rs[ii],tn);
if tn<min then begin
min:=tn;
xx:=ii;
end;
end;
for ii:=1 to length(rs[xx])-1 do
write(rs[xx][ii],' ');
write(rs[xx][length(rs[xx])]);
writeln;
close(output);
end.
1.4.3 Arithmetic Progressions 等差数列 ariprog
很暴力的题,5000ms时间。。。
求出双平方数集合,并进行flag标记,然后以每个数作为a,从1到(maxinset-a) div (n-1)枚举b,枚举n来判断是否这个数列的每个数都在集合中,最后进行多路排序输出,思路比较简单,理论上也没什么更好的算法,但是实现上有些技巧,似乎可以剪掉一部分a,所以大牛们似乎能在1s内过最大的一个点orz,而我没多想,只用了最简单的一些细节性的节约时间的技巧,我那个点是3s,次大的是2.6s,以后类似的题再注意吧,我不是求完美的人。。。不过给5s确实没必要吧。。。很吓人的
code:
{
ID: mfs.dev2
PROG: ariprog
LANG: PASCAL
}
program ariprog;
var
n,m,i,j,rc,dc,a,b,kk:longint;
r,fl:array[0..10001,1..2] of longint;
d:array[0..60000] of longint;
df:array[0..125000] of boolean;
flag:boolean;
procedure qs(s,t,k:longint);
var
i,j,t1,t2,x:longint;
begin
i:=s; j:=t; x:=r[(i+j) div 2,k];
repeat
while (r[i,k]<x) do inc(i);
while (r[j,k]>x) do dec(j);
if (i<=j) then begin
t1:=r[i,1]; r[i,1]:=r[j,1]; r[j,1]:=t1;
t2:=r[i,2]; r[i,2]:=r[j,2]; r[j,2]:=t2;
inc(i); dec(j);
end;
until i>j;
if (s<j) then qs(s,j,k);
if (i<t) then qs(i,t,k);
end;
begin
assign(input,'ariprog.in');
assign(output,'ariprog.out');
reset(input);rewrite(output);
readln(n);
readln(m);
fillchar(df,sizeof(df),0);
for i:=0 to m do
for j:=0 to m do
df[i*i+j*j]:=true;
for i:=0 to m*2*m do
if df[i] then begin
inc(dc);
d[dc]:=i;
end;
for j:=1 to dc do begin
a:=d[j];
for b:=1 to ((d[dc]-a) div (n-1)) do begin
flag:=true;
if a=1 then
a:=1;
for i:=1 to n-1 do begin
if not df[a+i*b] then begin
flag:=false;
break;
end;
end;
if flag then begin
inc(rc);
r[rc,1]:=a;
r[rc,2]:=b;
end;
end;
end;
if rc=0 then
writeln('NONE');
qs(1,rc,2);
for i:=1 to rc do begin
if (r[i,2]=r[i+1,2]) and (not (r[i,2]=r[i-1,2])) then begin
inc(kk);
fl[kk,1]:=i;
end;
if (not (r[i,2]=r[i+1,2])) and (r[i,2]=r[i-1,2]) then begin
fl[kk,2]:=i;
end;
end;
for i:=1 to kk do
qs(fl[i,1],fl[i,2],1);
for i:=1 to rc do
writeln(r[i,1],' ',r[i,2]);
close(output);
end.
1.4.4Mother's Milk 母亲的牛奶 milk3
最不WS的一个,貌似是当时讲搜索的例题?
深搜+三维的数组hash,比较简单,两次AC,就是注意dfs中的一些变量尽量用局部的或者拷贝到局部,不然有的虽然看起来不影响但是会出问题。
code
{
ID: mfs.dev2
PROG: milk3
LANG: PASCAL
}
program milk3;
type
v=array[1..3] of integer;
var
hash:array[0..20,0..20,0..20] of boolean;
r:array[0..21] of boolean;
m,s:v;
t:array[1..2] of integer;
rt:array[0..21] of integer;
i,c:integer;
procedure g(ta:integer);
begin
t[1]:=1;t[2]:=2;
if ta=1 then begin
t[1]:=2;t[2]:=3;
end;
if ta=2 then begin
t[1]:=1;t[2]:=3;
end;
end;
procedure dfs(k:v);
var
i,j:integer;
te:v;
tt:array[1..2] of integer;
begin
if hash[k[1],k[2],k[3]] then
exit;
hash[k[1],k[2],k[3]]:=true;
if k[1]=0 then
r[k[3]]:=true;
for i:=1 to 3 do
if k[i]>0 then begin
g(i);
tt[1]:=t[1];tt[2]:=t[2];
for j:=1 to 2 do begin
te:=k;
if m[tt[j]]-k[tt[j]]>=k[i] then begin
inc(te[tt[j]],k[i]);
te[i]:=0;
dfs(te);
end;
if m[tt[j]]-k[tt[j]]<k[i] then begin
te[tt[j]]:=m[tt[j]];
dec(te[i],m[tt[j]]-k[tt[j]]);
dfs(te);
end;
end;
end;
end;
begin
assign(input,'milk3.in');
assign(output,'milk3.out');
reset(input);rewrite(output);
for i:=1 to 3 do
read(m[i]);
fillchar(hash,sizeof(hash),0);
fillchar(r,sizeof(r),0);
s[1]:=0;s[2]:=0;s[3]:=m[3];
dfs(s);
for i:=0 to 21 do
if r[i] then begin
inc(c);
rt[c]:=i;
end;
for i:=1 to c-1 do
write(rt[i],' ');
writeln(rt[c]);
close(output);
end.

浙公网安备 33010602011771号