usaco第二章我的所有题解和程序(三) usaco 2.3
http://hi.baidu.com/mfs666/blog/item/d33d4f90d038098ba977a41a.html
usaco 2.3.1 The Longest Prefix (prefix)
串的动规,用了比较简单的一种一维动规,就是f[i]是这一位是否可以排出,状态转移是枚举每个元素串,如果有任何一个的减去长度的位置可以排成,且s[i]的后缀等与此元素串则f[i]为true,这样的时间复杂度是类似O(mn),但是实际上数据很弱,所以不用优化也绝对不会TLE(其实给的数据最大的用了0.1S),但是其实有很多优化可以用,比如元素串最大长度为10,所以如果连续10个f都是false,就可以停止了,还可以用字符串的hash或者trie树优化枚举,那样就成了o(10n)了。
code
{
ID: mfs.dev2
PROG: prefix
LANG: PASCAL
}
program prefix;
var
m:array[0..201] of string;
l:array[0..201] of longint;
f:array[-1..200001] of boolean;
s,si:ansistring;
i,j,c,r,k:longint;
begin
assign(input,'prefix.in');
assign(output,'prefix.out');
reset(input);rewrite(output);
readln(si);
c:=1;
while si<>'.' do begin
for i:=1 to length(si) do begin
if si[i]=' ' then begin
inc(c);
continue;
end;
m[c]:=m[c]+si[i];
end;
readln(si);
inc(c);
end;
while not eof do begin
readln(si);
s:=s+si;
end;
readln(si);
s:=s+si;
fillchar(f,sizeof(f),0);
f[0]:=true;
for i:=1 to c do
l[i]:=length(m[i]);
k:=length(s);
for i:=1 to k do begin
for j:=1 to c do begin
if i-l[j]<0 then
continue;
if f[i-l[j]] then
if copy(s,i-l[j]+1,l[j])=m[j] then begin
f[i]:=true;
break;
end;
end;
end;
for i:=1 to k do
if f[i] then
r:=i;
writeln(r);
close(output);
end.
usaco 2.3.2 Cow Pedigrees (nocows)
囧动态规划,树的计数问题,但是没想到方程,看题解,也不给个具体的说明或者证明,而且取模的性质我也不了解,先囧着吧,等找只神牛问。。。
code
{
ID: mfs.dev2
PROG: nocows
LANG: PASCAL
}
program nocows;
var
f:array[-2..201,0..101] of longint;
i,j,o,n,k,r:longint;
begin
assign(input,'nocows.in');
assign(output,'nocows.out');
reset(input);
rewrite(output);
readln(n,k);
for i:=1 to k do
f[1,i]:=1;
f[1,1]:=1;
for i:=1 to n do
for j:=1 to k do
for o:=1 to i-2 do
inc(f[i,j],f[o,j-1]*f[i-1-o,j-1] mod 9901);
r:=f[n,k]-f[n,k-1];
while r<0 do
inc(r,9901);
r:=r mod 9901;
writeln(r);
close(output);
end.
usaco 2.3.3 Zero Sum (zerosum)
模拟和深搜,时间复杂度没问题,水题,一次AC。
code
{
ID: mfs.dev2
PROG: zerosum
LANG: PASCAL
}
program zerosum;
var
i,n,j,cn:longint;
s,t:string;
rs:array[0..10000] of string;
procedure ca;
var
a,c:string;
b,bb,x,y,i,h:longint;
begin
h:=0;
a:='';
c:='';
for i:=1 to length(s) do
begin
if s[i] in ['1'..'9'] then begin
if h=1 then
c:=c+s[i];
if h=0 then
a:=a+s[i];
end
else begin
bb:=b;
case s[i] of
'+': b:=1;
'-': b:=2;
end;
if h=0 then begin
h:=1;
continue;
end;
val(a,x);val(c,y);
if bb=1 then
x:=x+y
else
x:=x-y;
str(x,a);c:='';
end;
end;
x:=0;y:=0;
val(a,x);val(c,y);
if h=1 then
if b=1 then
x:=x+y
else
x:=x-y;
if x=0 then begin
inc(cn);
rs[cn]:=s;
end;
end;
procedure dfs(p:integer);
var
t,tn,sc:string;
i:integer;
begin
if p>n-2 then begin
ca;
exit;
end;
sc:=s;
for i:=1 to 3 do begin
case i of
1:t:='+';
2:t:='-';
3:t:='';
end;
str(p+2,tn);
s:=s+t+tn;
dfs(P+1);
s:=sc;
end;
end;
begin
assign(input,'zerosum.in');
assign(output,'zerosum.out');
reset(input);rewrite(output);
readln(n);
s:='1';
dfs(0);
for i:=1 to cn do begin
j:=2;
while j<=length(rs[i]) do begin
if (rs[i][j] in ['1'..'9']) and (rs[i][j-1] in ['1'..'9']) then
insert(' ',rs[i],j);
inc(j);
end;
end;
for i:=1 to cn-1 do
for j:=i+1 to cn do
if rs[j]<rs[i] then begin
t:=rs[j];
rs[j]:=rs[i];
rs[i]:=t;
end;
for i:=1 to cn do
writeln(rs[i]);
close(output);
end.
usaco 2.3.4 Money Systems (money)
动规问题,无限背包的计数。自己想的方程是这样:
for i:=1 to v do
for j:=1 to n do begin
t:=j;
o:=0;
while t>=0 do begin
inc(f[i,j],f[i-1,t]);
inc(o);
t:=j-m[i]*o;
end;
end;
但是太垃圾了,有一个点会TLE(就是那种数据会使里面的while循环执行很多次),于是看题解得到O(mn)的方程,也就是
for i:=1 to m do
for j:=0 to n do
begin
dp[i,j]:=dp[i-1,j];
if j-cc[i]>=0 then
dp[i,j]:=dp[i,j]+dp[i,j-cc[i]];
end;
根据hzhua神牛的解释,这个就是转化为01背包,dp[i,j]:=dp[i,j]+dp[i,j-cc[i]];中的dp[i,j-cc[i]]意思就是用过了i,还想用i(01的是f[i-1,j-cc[i]])这样每次都是再决定是否再用一次,而之前的用这个组成的更小的容量已经求出,转移即可。要注意f[0,0]也要初始化成1,而后面会求到的几个要注意不要犯傻也把它初始化了,那样就重复了。初始化要注意只能处理会用到但不会求的边界。
code:
{
ID: mfs.dev2
PROG: money
LANG: PASCAL
}
var
v,i,m:byte;
n,j:word;
cc:array[1..25]of word;
dp:array[0..25,0..10000]of int64;
begin
assign(input,'money.in');
assign(output,'money.out');
reset(input);
rewrite(output);
readln(v,n);
fillchar(dp,sizeof(dp),0);
m:=1;
for i:=1 to v do
begin
read(cc[m]);
for j:=1 to m do
if cc[j]=cc[i] then
break;
if j=m then
inc(m);
end;
dec(m);
readln;
for i:=1 to v do
dp[i,0]:=1;
dp[0,0]:=1;
for i:=1 to m do
for j:=0 to n do
begin
dp[i,j]:=dp[i-1,j];
if j-cc[i]>=0 then
dp[i,j]:=dp[i,j]+dp[i,j-cc[i]];
end;
writeln(dp[m,n]);
close(output);
end.
usaco 2.3.5 Controlling Companies (concom)
有意思的题。这个题的我的算法类似佛洛伊德,时间近似O(n^3)但是其实实际上要低得多,而且n最大100,时间复杂度不存在任何问题。开邻接矩阵记录原始股(囧词。。。),同时开邻接表记录每个公司的控制名单,然后再开一个邻接矩阵,记录每个控制关系是否已经存在。每次扫描全部公司,按照给出的规则看看是否会有之前没有存在的控制关系出现,如果有则增加计数器,同时计入邻接矩阵和对应公司的邻接表,重复这个过程直到没有新的控制关系出现。这样只要有的控制都会被记录,如果没有新的控制,那么结果就不会再改变,这样就退出,真的很类似弗洛伊德算法的精神。。。
其实还可以写出类似松弛操作的算法(记录新增的控制关系,下一次只检查有关的公司),那样会更快,不过没有必要啦。
code:
{
ID: mfs.dev2
PROG: concom
LANG: PASCAL
}
program concom;
type v=record
n:integer;
d:array[0..101] of integer;
end;
var
rt,re:array[1..101,1..101] of integer;
rx:array[1..101] of v;
i,j,n,l1,l2,b,cc:integer;
function ga(x,y:integer):integer;
var
i:integer;
begin
ga:=0;
for i:=1 to rx[x].n do
inc(ga,rt[rx[x].d[i],y]);
end;
begin
assign(input,'concom.in');
assign(output,'concom.out');
reset(input);
rewrite(output);
readln(n);
for i:=1 to n do begin
readln(l1,l2,b);
rt[l1,l2]:=b;
{if b>50 then begin
inc(cc);inc(c);
rc[cc]:=l1;
rd[cc]:=l2;
ra[cc]:=l1;
rb[cc]:=l2;
re[l1,l2]:=1;
end;}
end;
for i:=1 to 101 do begin
rt[i,i]:=100;
re[i,i]:=1;
rx[i].n:=1;
rx[i].d[1]:=i;
end;
cc:=1;
while cc>0 do begin
cc:=0;
for i:=1 to 100 do
for j:=1 to 100 do
if (re[i,j]=0) and (ga(i,j)>50) then
begin
re[i,j]:=1;
inc(cc);
inc(rx[i].n);
rx[i].d[rx[i].n]:=j;
end;
end;
for i:=1 to 100 do
for j:=1 to 100 do
if (re[i,j]=1) and (i<>j) then
writeln(i,' ',j);
close(output);
end.