这两题都体现了dp的核心:状态
dp做多就发现,状态一设计出来,后面的什么都迎刃而解了(当然需要优化的还要动动脑筋);
先说比较简单的;
poj2436 由题得知病毒种数<=15很小,于是我们就容易想到将病每个牛携带的病毒抽象成15位的二进制数
0表示这种病毒不存在,1表示病毒存在,于是设cowi携带的病毒转化为二进制数为a,cowj为b
则如果挤cowi和cowj,那么所含病毒数为a or b,由此dp就显然了;
1 var dp:array[0..1,0..40000] of longint; //表示所含病毒数为j的情况下最多可以挤多少只牛 2 w,f:array[0..40000] of longint; 3 s,ans,x,i,j,n,m,k,t,y,k1,k2:longint; 4 5 function prework(x:longint):longint; //预处理数字代表的病毒种类数目 6 var t:longint; 7 begin 8 t:=0; 9 while x<>0 do 10 begin 11 t:=t+x mod 2; 12 x:=x div 2; 13 end; 14 exit(t); 15 end; 16 17 begin 18 readln(t,m,k); 19 s:=1 shl m-1; 20 f[0]:=0; 21 for i:=0 to s do 22 begin 23 f[i]:=prework(i); 24 dp[0,i]:=-1; 25 end; 26 for i:=1 to t do 27 begin 28 read(x); 29 if x>k then readln 30 else begin 31 n:=n+1; 32 w[n]:=0; 33 for j:=1 to x do //转化为二进制 34 begin 35 read(y); 36 w[n]:=w[n]+1 shl (y-1); 37 end; 38 end; 39 end; 40 dp[0,0]:=0; 41 k1:=1; 42 k2:=0; 43 ans:=0; 44 for i:=1 to n do //dp 45 begin 46 k1:=k1 xor 1; //滚动数组 47 k2:=k2 xor 1; 48 dp[k2]:=dp[k1]; 49 for j:=0 to s do 50 begin 51 x:=j or w[i]; //位运算 52 if (f[x]<=k) and (f[j]<=k) and (dp[k1,j]>=0) then 53 dp[k2,x]:=max(dp[k2,x],dp[k1,j]+1); 54 ans:=max(dp[k2,x],ans); 55 end; 56 end; 57 writeln(ans); 58 end.
写的比较随意,有些地方还可以优化 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)
1 const max=30000001; 2 var f:array[0..1,0..1010,0..4] of longint; 3 a,b:array[0..1010] of longint; 4 i,j,k1,k2,n,k,m,w,ans,t:longint; 5 function findmin(t:longint):longint; 6 var p,k:longint; 7 begin 8 p:=max; 9 for k:=1 to 4 do 10 p:=min(p,f[k1,j-t,k]); 11 exit(p); 12 end; 13 procedure sort(l,r: longint); //按列排序,上下都有牛时1在前2在后,这样处理dp的时候方便多 14 var i,j,x,y: longint; 15 begin 16 i:=l; 17 j:=r; 18 x:=a[(l+r) div 2]; 19 y:=b[(l+r) div 2]; 20 repeat 21 while (a[i]<x) or (a[i]=x) and (b[i]<y) do inc(i); 22 while (x<a[j]) or (a[j]=x) and (b[j]>y) do dec(j); 23 if not(i>j) then 24 begin 25 swap(a[i],a[j]); 26 swap(b[i],b[j]); 27 inc(i); 28 j:=j-1; 29 end; 30 until i>j; 31 if l<j then sort(l,j); 32 if i<r then sort(i,r); 33 end; 34 35 procedure doit1; 36 begin 37 f[k2,j,1]:=min(f[k2,j,1],f[k1,j,1]+2*(a[i]-a[i-1])); 38 f[k2,j,1]:=min(findmin(1)+2,f[k2,j,1]); 39 end; 40 41 procedure doit2; 42 var p:longint; 43 begin 44 f[k2,j,2]:=min(f[k1,j,2]+2*(a[i]-a[i-1]),f[k2,j,2]); 45 p:=min(min(f[k1,j-1,2],f[k1,j-1,3]),f[k1,j-1,4]); 46 f[k2,j,2]:=min(f[k2,j,2],p+a[i]-a[i-1]+1); 47 if j-2>=0 then 48 f[k2,j,2]:=min(f[k2,j,2],findmin(2)+2); 49 end; 50 51 procedure doit(x:longint); 52 var p:longint; 53 begin 54 p:=min(f[k1,j,x],f[k1,j,2]); 55 f[k2,j,x]:=min(f[k2,j,x],p+a[i]-a[i-1]); 56 f[k2,j,x]:=min(f[k2,j,x],findmin(1)+1); 57 end; 58 59 begin 60 readln(n,k,m); 61 for i:=1 to n do 62 readln(b[i],a[i]); 63 sort(1,n); 64 for i:=0 to k do //初始化 65 for j:=1 to 4 do 66 begin 67 f[0,i,j]:=max; 68 f[1,i,j]:=max; 69 end; 70 i:=1; 71 f[0,1,1]:=2; 72 if a[i]=a[i+1] then 73 begin 74 i:=3; 75 f[0,2,2]:=2; 76 end 77 else begin 78 i:=2; 79 if b[1]=1 then f[0,1,3]:=1 80 else f[0,1,4]:=1; 81 end; 82 k1:=1; 83 k2:=0; 84 while i<=n do 85 begin 86 k1:=k1 xor 1; //滚动数组 87 k2:=k2 xor 1; 88 if a[i]=a[i+1] then w:=2 else w:=1; 89 for j:=1 to k do 90 begin 91 for t:=1 to 4 do 92 f[k2,j,t]:=max; 93 doit1; //做4个状态,方程式自己动手比划一下就明白了 94 doit2; 95 if w=1 then 96 begin 97 if b[i]=1 then 98 doit(3) 99 else doit(4); 100 end; 101 end; 102 i:=i+w; 103 end; 104 ans:=max; 105 for i:=1 to 4 do 106 ans:=min(ans,f[k2,k,i]); 107 writeln(ans); 108 end.
上述三道题都是非常好的设计状态题,状态是用dp解决问题的关键
特别poj2430一设计出状态,dp立马迎刃而解