这两题都体现了dp的核心:状态
dp做多就发现,状态一设计出来,后面的什么都迎刃而解了(当然需要优化的还要动动脑筋);
先说比较简单的;
poj2436 由题得知病毒种数<=15很小,于是我们就容易想到将病每个牛携带的病毒抽象成15位的二进制数
0表示这种病毒不存在,1表示病毒存在,于是设cowi携带的病毒转化为二进制数为a,cowj为b
则如果挤cowi和cowj,那么所含病毒数为a or b,由此dp就显然了;
var dp:array[..,..] of longint; //表示所含病毒数为j的情况下最多可以挤多少只牛
w,f:array[..] of longint;
s,ans,x,i,j,n,m,k,t,y,k1,k2:longint; function prework(x:longint):longint; //预处理数字代表的病毒种类数目
var t:longint;
begin
t:=;
while x<> do
begin
t:=t+x mod ;
x:=x div ;
end;
exit(t);
end; begin
readln(t,m,k);
s:= shl m-;
f[]:=;
for i:= to s do
begin
f[i]:=prework(i);
dp[,i]:=-;
end;
for i:= to t do
begin
read(x);
if x>k then readln
else begin
n:=n+;
w[n]:=;
for j:= to x do //转化为二进制
begin
read(y);
w[n]:=w[n]+ shl (y-);
end;
end;
end;
dp[,]:=;
k1:=;
k2:=;
ans:=;
for i:= to n do //dp
begin
k1:=k1 xor ; //滚动数组
k2:=k2 xor ;
dp[k2]:=dp[k1];
for j:= to s do
begin
x:=j or w[i]; //位运算
if (f[x]<=k) and (f[j]<=k) and (dp[k1,j]>=) then
dp[k2,x]:=max(dp[k2,x],dp[k1,j]+);
ans:=max(dp[k2,x],ans);
end;
end;
writeln(ans);
end.
poj2436
写的比较随意,有些地方还可以优化 O(n*2^m)是可以在1s出来的,实际813ms(悬)
poj3659是树的最小支配集
这题贪心也是正确方法,但这里我介绍的是treedp
对于当前节点i,容易想的两种状态f[i,1]表示在i节点建立发射塔(属于支配集)
所以它的孩子节点状态就随意了,每个子节点选个最小的状态就行了;
f[i,0]表示在i节点不建立发射塔,但i和其子树都被覆盖了
所以f[i,0]=signma(min(f[j,0],f[j,1]))-min(f[k,0]-f[k,1]); j表示i的所有孩子,而k表示其中有一个孩子一定要属于支配集
注意不要漏了一种情况:那就是i可以不被儿子覆盖而被它的父亲覆盖,并且它的子树都是全覆盖的
f[i,2]=signma(min(f[j,0],f[j,1]))
最后的ans=min(f[root,0],f[root,1]);
实现起来还是很容易的,dfs到叶子然后想上更新
值得注意的是,题目给出的相邻结点而非父子关系,所以用图的形式存然后以任意一点做treedp即可
poj2430是使用k个矩形覆盖所用奶牛的最小覆盖面积
同样要设计好状态
设f[i,j,k]表示到覆盖到第i只奶牛使用j个木板,k表示覆盖结尾的状态,显然有4种
1.上下都圈而且上下是属于同一次圈的
2.上下都圈而且上下是不属于同一次圈的,属于两次圈的
3.只圈上面的
4.只圈下面的
然后就轻松了
code:复杂度O(nk+nlog2n)
const max=;
var f:array[..,..,..] of longint;
a,b:array[..] of longint;
i,j,k1,k2,n,k,m,w,ans,t:longint;
function findmin(t:longint):longint;
var p,k:longint;
begin
p:=max;
for k:= to do
p:=min(p,f[k1,j-t,k]);
exit(p);
end;
procedure sort(l,r: longint); //按列排序,上下都有牛时1在前2在后,这样处理dp的时候方便多
var i,j,x,y: longint;
begin
i:=l;
j:=r;
x:=a[(l+r) div ];
y:=b[(l+r) div ];
repeat
while (a[i]<x) or (a[i]=x) and (b[i]<y) do inc(i);
while (x<a[j]) or (a[j]=x) and (b[j]>y) do dec(j);
if not(i>j) then
begin
swap(a[i],a[j]);
swap(b[i],b[j]);
inc(i);
j:=j-;
end;
until i>j;
if l<j then sort(l,j);
if i<r then sort(i,r);
end; procedure doit1;
begin
f[k2,j,]:=min(f[k2,j,],f[k1,j,]+*(a[i]-a[i-]));
f[k2,j,]:=min(findmin()+,f[k2,j,]);
end; procedure doit2;
var p:longint;
begin
f[k2,j,]:=min(f[k1,j,]+*(a[i]-a[i-]),f[k2,j,]);
p:=min(min(f[k1,j-,],f[k1,j-,]),f[k1,j-,]);
f[k2,j,]:=min(f[k2,j,],p+a[i]-a[i-]+);
if j->= then
f[k2,j,]:=min(f[k2,j,],findmin()+);
end; procedure doit(x:longint);
var p:longint;
begin
p:=min(f[k1,j,x],f[k1,j,]);
f[k2,j,x]:=min(f[k2,j,x],p+a[i]-a[i-]);
f[k2,j,x]:=min(f[k2,j,x],findmin()+);
end; begin
readln(n,k,m);
for i:= to n do
readln(b[i],a[i]);
sort(,n);
for i:= to k do //初始化
for j:= to do
begin
f[,i,j]:=max;
f[,i,j]:=max;
end;
i:=;
f[,,]:=;
if a[i]=a[i+] then
begin
i:=;
f[,,]:=;
end
else begin
i:=;
if b[]= then f[,,]:=
else f[,,]:=;
end;
k1:=;
k2:=;
while i<=n do
begin
k1:=k1 xor ; //滚动数组
k2:=k2 xor ;
if a[i]=a[i+] then w:= else w:=;
for j:= to k do
begin
for t:= to do
f[k2,j,t]:=max;
doit1; //做4个状态,方程式自己动手比划一下就明白了
doit2;
if w= then
begin
if b[i]= then
doit()
else doit();
end;
end;
i:=i+w;
end;
ans:=max;
for i:= to do
ans:=min(ans,f[k2,k,i]);
writeln(ans);
end.
上述三道题都是非常好的设计状态题,状态是用dp解决问题的关键
特别poj2430一设计出状态,dp立马迎刃而解