浅谈DP
来源:互联网 发布:一个人能备案几个域名 编辑:程序博客网 时间:2024/06/18 03:10
DP
这种思维方式,以及技巧,是极其灵活的,也是非常有趣的.
立下此
前言:
掌握
难度:
T1:
Problem:
给定一个长度为
Data Constraint:
对于50%的数据,
对于100%的数据,
Solution:
50%:
对于
转移:
100%:
那么对于
考虑另设状态.
我们设
那么转移就显然了:
对于每一个
故总的时间复杂度时大致为
Code:
var a,f:array[1..100000] of longint; n,i,k,len:longint; function find(len,n:longint):Longint; var l,r,mid:longint; begin l:=1; r:=len; while l<=r do //注意这里的=号,不可省略,举个例子你就知道为什么了 begin mid:=(l+r) div 2; if f[mid]>n then r:=mid-1 else l:=mid+1; //注意这里的>号,我们找的是最长不下降,故当f[mid]<=n时,l的值都应该增加. end; exit(l); //f[l]即ai能更新的最远位置,此二分能保证f[l-1]<=n,且f[l-1]<>f[l],故f[l]一定可被更新.end; begin readln(n); for i:=1 to n do read(a[i]); len:=1; f[1]:=a[1]; for i:=2 to n do begin k:=find(len,a[i]); f[k]:=a[i]; if k>len then len:=k; //即更新f[l]和最长不下降串的长度len end; writeln(len); end.
difficulty:*
T2:
link:
https://jzoj.net/senior/#contest/show/1923/1
Problem:
给定一个长度为
Data constraint:
数据保证不同的
Solution:
一个显然的性质——对于此题,我们只需考虑单调不减,因为把序列反过来后就成了单调不增.
75%:
那么,对于
而最长不下降子序列是可以在
期望得分:60~75
100%:
最长不下降子序列其实是一种类似贪心的
那怎么让它变得有后效性呢?
其实我们仔细想想,可以发现,每一个数都是针对其前一个数对应而言的, 也就是对于每一个数只要不要小于其前一个数即可.
一种极其简单的设法:我们可以设状态
可是
怎么办?
想到不同的
那么转移显然:
时间复杂度:
空间复杂度:
difficulty:**
T3
link:
https://jzoj.net/senior/#main/show/3735
Problem:
求
有趣数的定义如下:若一个数字至少有一半以上的数位上拥有相同的数字,则这个数为有趣数.
Data constraint:
对于20%的数据,
对于30%的数据,
对于40%的数据,
对于100%的数据,
Solution:
20%:
直接模拟即可
30%:
我们转化数字的时候不用字符串,用整数代替可以加快速度,简单说就是字符串太慢.
40%
显然,此题满足区间减法,即
可以想到数位
100%
很明显,这是一道数位
Wrong method:
当得知是数位DP后,我们很容易可以想到这么设状态:
设
最后结果就是
这样子设状态,仿佛把问题简化了许多,但我们看看到底为什么不能这么设状态.
这么设状态,我们需要枚举第
错误原因:状态设错,导致无从转移.
解法一:
设此状态时我们可以采取数位
假设我求
设完状态之后,我们总感觉少了点什么,哦,这道题要我们求相同数字的个数要至少占整个数长度的一半,那我就把状态设成
可通过上面的
那问题来了,怎么避免这种重复?
我们想,对于每一个有趣数,至多有两个占一半数位的数字,言下之意就是我们只需枚举这个占一半数位的数字,然后再减去特殊情况有两个数字的即可.
那么我们设
这样设了状态之后,转移就变得简单了,不过还有很多的细节之处需要注意,如为什么要有前导0,它的作用是什么,怎么处理其对应的情况,我们在
Code-1
var ss:string; s:array[0..20] of int64; f:array[0..20,0..1,0..1,-40..40] of int64; t:array[0..9] of int64; i,j,k,p,same1,zero1,key1,same2,next:longint; zero2,key2,ans,tot,x,y:int64;function fans(x1,x2:int64):int64;begin fillchar(f,sizeof(f),0); f[0,0,1,0]:=1; //此状态很重要,及通过下面一样的以前导0的思想,导出所有的状态. fans:=0; for i:=0 to length(ss)-1 do for same1:=0 to 1 do for zero1:=0 to 1 do for key1:=-20 to 20 do for next:=0 to 9 do begin if (x1+x2>0) and ((next>0) or (zero1=0)) and (next<>x1) and (next<>x2) then continue; //这里是判断特殊情况,即有两个关键字时,当枚举的不是前导0时,next必须为x1或x2. if (same1=0) and (next>s[i+1]) then break; //当构造的数已经大于原数则可以退出循环. if (same1=1) or (next<s[i+1]) then same2:=1 else same2:=0; //显然. if (zero1=1) and (next=0) then zero2:=1 else zero2:=0; //这里是重点,即前导0的用处,只有当(zero1=1)and(next=0)时,这个数就是0,则这个数可以作为一个前导,来“导出”下面一些不足n位的数,使得可以一次算尽所有1~x里的所有数. key2:=key1; if (zero2=0) then begin if (next=k) then key2:=key1+1 else key2:=key1-1; end; //只有当不为前导时,才有关键字可言. inc(f[i+1,same2,zero2,key2],f[i,same1,zero1,key1]); //转移. end; for same1:=0 to 1 do for key1:=0 to 20 do begin if (x1+x2>0) and (key1>0) then break; //即处理有两个关键字时,key1的值只能为0. inc(fans,f[length(ss),same1,0,key1]); //累加答案. end;end;function answer(x:int64):int64;begin str(x,ss); for i:=1 to length(ss) do s[i]:=ord(ss[i])-48; //转化 answer:=0; for k:=0 to 9 do inc(answer,fans(0,0)); //处理关键字k for k:=0 to 9 do for p:=k+1 to 9 do dec(answer,fans(k,p)); //处理含有两个关键字的有趣数end;begin assign(input,'odometer.in'); reset(input); assign(output,'odometer.out'); rewrite(output); readln(x,y); writeln(answer(y)-answer(x-1)); //即满足区间减法 close(input); close(output);end.
difficulty:***
T4
Link:
https://jzoj.net/senior/#contest/show/1947/1
Problem:
给定正整数
Data Constraint:
对于
对于
对于
Solution:
50%-Wrong method:
第一次做时,我是想到了类似周期的方法,但很明显这种方法是错的,例如数据如下:
按照周期思想,
100%:
因为
其中当
显然有方程:
最后直接判断
现在麻烦的是,需要分类讨论来解答
但其实有一种及其简单又有效的方法,即把
Code:
var a,c:array[1..4] of longint; f:array[0..1000,-1..9,-1..9,-1..9,-1..9] of longint; n,m,q,i,j,k,p,sum,t,time:longint;function max(x,y:longint):longint;begin if x>y then exit(x) else exit(y);end;begin readln(n,m,q); for i:=1 to n do read(a[i]); for i:=n+1 to 4 do a[i]:=9; for i:=1 to n do read(c[i]); fillchar(f,sizeof(f),255); f[0,a[1],a[2],a[3],a[4]]:=q; for time:=0 to m-2 do for i:=1 to a[1] do for j:=1 to a[2] do for k:=1 to a[3] do for p:=1 to a[4] do begin f[time+1,i-1,j-1,k-1,p-1]:=max(f[time+1,i-1,j-1,k-1,p-1],f[time,i,j,k,p]); f[time+1,a[1],j-1,k-1,p-1]:=max(f[time+1,a[1],j-1,k-1,p-1],f[time,i,j,k,p]-c[1]); f[time+1,i-1,a[2],k-1,p-1]:=max(f[time+1,i-1,a[2],k-1,p-1],f[time,i,j,k,p]-c[2]); f[time+1,i-1,j-1,a[3],p-1]:=max(f[time+1,i-1,j-1,a[3],p-1],f[time,i,j,k,p]-c[3]); f[time+1,i-1,j-1,k-1,a[4]]:=max(f[time+1,i-1,j-1,k-1,a[4]],f[time,i,j,k,p]-c[4]); if f[time,i,j,k,p]>=0 then t:=time; end; sum:=-2; for i:=1 to a[1] do for j:=1 to a[2] do for k:=1 to a[3] do for p:=1 to a[4] do if f[m-1,i,j,k,p]>sum then sum:=f[m-1,i,j,k,p]; if sum>-1 then begin writeln('+oo'); writeln(sum); end else begin writeln(t); for i:=1 to a[1] do for j:=1 to a[2] do for k:=1 to a[3] do for p:=1 to a[4] do if (f[t,i,j,k,p]>sum) then sum:=f[t,i,j,k,p]; writeln(sum); end;end.
difficulty:**
T5
Link
https://jzoj.net/senior/#main/show/1340
Problem
给定
Data constraint
对于
对于
对于
difficulty:*
T6
Link
https://jzoj.net/senior/#main/show/1397
Problem
给定一个环,指针一开始指向第一个数,每次可以取指针周围相邻的
求当所有数都被选完后所需的最小费用.
Data constraint
对于20%的数据,n≤10,k≤3;
对于40%的数据,n≤100,k≤10;
对于60%的数据,n≤500,k≤20;
对于100%的数据,n≤2000,k≤500;
Solution
这道题比赛时就知道是DP了,但是这题的DP很巧,思维很缜密,值得好好总结.
思维策略:
因为选数的代价是一定的,故可以把问题转化为如何转动指针使得代价最小.
而我们知道,每次取数时,把能取的数都取了一定是最优的.
所以我们先把
状态:
设
设
状态设的很巧!
转移:
这里的
理解:
只讨论第一个状态转移的思维方式,另一状态是一样的.
①
在
②
在
很明显不用走的路程是
③
状态
整体思想:
所以让我们分析一下这题的整体思想——把状态设好后,把转移分成两步,一、往当前指针方向走一步,二、跳到链的另一端并继续走一步.
其中跳到链的另一端时又分两种情况讨论,讨论所需要走的步数!
总结:
此题重在思维,实现起来很简单,但还需要注意一些细节,如区间最大值的预处理,一些不可能转移状态的预处理,设为无穷大,最后
DP的思路是很广的,关键在于如何挖掘题目的已有条件,挖掘隐含的性质,把问题简单化,设下状态转移.
这题的状态设的很巧,以至于把这题完完整整的想了三天才明白,是一道好题!
difficulty:***
T7
Link
https://jzoj.net/senior/#main/show/1667
Problem
在n*m的棋盘里能放多少个炮.
Data constraint
除了在3个格子中都放满炮的的情况外,其它的都可以.
100%的数据中N,M不超过100
50%的数据中,N,M至少有一个数不超过8
30%的数据中,N,M均不超过6
Solution
一开始看题就联想到了很多有关于类似放马,放国王的问题.
放马的是可以通过找规律得出答案,放国王的可以搜索.
但当看到数据之后,瞬间醒悟。
容易发现,其实题目可以转化为在一个
那就按照想法设状态-设
注意,不能用
dp的时候注意一些细节就好了,总共分六类情况讨论,注意没有“一行同竖着放两个棋”的奇葩转移.
diffcultly:**
T8
Link
https://jzoj.net/senior/#main/show/1330
Problem
给定一个仅包含j,z两个字符的字符串,可以交换两个字符
Data constraint
【数据规模】
对于10%的数据,有N≤10;
对于30%的数据,有K≤10;
对于40%的数据,有N≤50;
对于100%的数据,有N≤500,K≤100。
Solution
这道题的思路很巧妙.是一道好题.
首先,我们先看看交换两个字符这一操作是否可以更改.
因为我发现如果就以交换两个字符去做,很难设状态,并且很难转移.
并且这还不符合dp的无后效性.
但其实我们可以把它转化成无后效性.
以最一般的思路设状态,设
先不考虑转移,当这个状态中的
于是转移我们就可以分四种情况讨论,按照
这里转移有个技巧,就是由
除了状态设的巧妙,转移巧妙,这题还让我领悟到
一定要注意这些细节,比如说当j,k小于0时,是不能作为状态的,当j=k=0时,也是需要特判.
并且这题还有最重要的一个细节,由于状态的巧妙,当一个串中一个字符的个数都已经小于答案了,那么就不能置换,所以答案一定不能大于
Code
uses math;var l:array[0..500] of longint; f:array[0..500,-1..100,-1..100] of longint; s:ansistring; n,k,i,j,t,ans,tot1,tot2,kk:longint;begin readln(n,t); readln(s); for i:=1 to length(s)-1 do begin if (s[i]='j') and (s[i+1]='z') then inc(ans); l[i+1]:=ans; if s[i]='j' then inc(tot1) else inc(tot2); end; if s[i+1]='j' then inc(tot1) else inc(tot2); kk:=min(tot1,tot2); for j:=0 to t do for k:=0 to t do f[0,j,k]:=-maxlongint; f[0,0,0]:=0; for i:=2 to n do for j:=0 to min(i,t) do for k:=0 to min(i,t) do begin if j+k=0 then begin f[i,j,k]:=l[i]; continue; end; if (s[i-1]='j') then begin if s[i]='z' then f[i,j,k]:=f[i-2,j,k]+1 else begin if j>0 then f[i,j,k]:=f[i-2,j-1,k]+1; end; end else begin if s[i]='z' then begin if (k>0) then f[i,j,k]:=f[i-2,j,k-1]+1; end else begin if (j>0) and (k>0) then f[i,j,k]:=f[i-2,j-1,k-1]+1; end; end; f[i,j,k]:=max(f[i,j,k],f[i-1,j,k]); end; for i:=0 to t do if (f[n,i,i]>ans) then ans:=f[n,i,i]; writeln(min(ans,kk));end.
diffcultly:***
T9
Link
https://jzoj.net/senior/#main/show/1331
Problem
给定n个i*100米的高度,再给定一个初始能量,每次最多跳不超过能量的米数,跳到第i个高度会有一定的收益,保证n个高度上的收益都能拿到,求最后最大的收益.
Data constraint
对于10%的数据,有N≤10;
对于20%的数据,有N≤100;
对于40%的数据,有N≤1000;
对于70%的数据,有N≤100000;
对于100%的数据,有N≤2000000。
保证对于所有数据,教主都能吃到所有的能量球,并且能量球包含的能量之和不超过2^31-1。
Solution
很容易想到状态:
设
很明显:
复杂度
很容易又发现,每次跳一定会比上次跳的高,所以跳了几次这一维可以省略.
于是复杂度变成
再很容易发现,方程可以用线段树维护,但由于线段树常数太大,可以拿70~80分.
再观察一遍
若
即
const maxn=2000000;var sum,f,a,d,s:array[0..maxn] of longint; n,m,i,h,t:longint;begin readln(n,m); for i:=1 to n do begin read(a[i]); sum[i]:=sum[i-1]+a[i]; end; d[0]:=m; s[0]:=m; h:=0; t:=1; for i:=1 to n do begin while (s[h]<i*100) and (h<t) do inc(h); f[i]:=sum[i]-i*100+d[h]; while (f[i]-sum[i]>d[t]) and (t>0) do dec(t); inc(t); d[t]:=f[i]-sum[i]; s[t]:=f[i]; end; writeln(f[n]);end.
但其实单调队列并没有什么实际用处,这道题之所以能优化成
每次只要跳到比上次高且最矮的地方就好了.
const maxn=2000000;var sum:array[0..maxn] of longint; f:array[0..maxn] of longint; A:array[1..maxn] of longint; n,m,i,j,k,w,ans:longint;begin readln(n,m); for i:=1 to n do begin read(a[i]); sum[i]:=sum[i-1]+a[i]; end; f[0]:=m; w:=0; for j:=1 to n do for k:=w to j-1 do if (f[k]>=j*100) then begin f[j]:=sum[j]-j*100+f[k]-sum[k]; w:=k; break; end; writeln(f[n]);end.
线段树版本:
const maxn=2000000;var tree:array[0..2*maxn+1000000] of longint; A,sum,f:array[0..maxn] of longint; n,m,i,j,s,t:longint;function max(x,y:longint):longint;begin if x>y then exit(x) else exit(y);end;procedure modify(x,l,r,s:longint);var mid:LONGINT;begin if l=r then tree[x]:=max(tree[x],s) else begin mid:=(l+r) shr 1; if mid>=t then modify(x shl 1,l,mid,s) else modify(x shl 1+1,mid+1,r,s); tree[x]:=max(tree[x shl 1],tree[x shl 1+1]); end;end;procedure find(x,st,en,l,r:longint);var mid:longint;begin if (st=l) and (en=r) then s:=max(s,tree[x]) else begin mid:=(st+en) shr 1; if mid>=r then find(x shl 1,st,mid,l,r) else if mid<l then find(x shl 1+1,mid+1,en,l,r) else begin find(x shl 1,st,mid,l,mid); find(x shl 1+1,mid+1,en,mid+1,r); end; end;end;begin readln(n,m); for i:=1 to n do begin read(a[i]); sum[i]:=sum[i-1]+a[i]; end; fillchar(tree,sizeof(tree),$80); f[1]:=a[1]+m-100; t:=f[1] div 100; modify(1,1,n,f[1]-a[1]); for j:=2 to n do begin s:=-maxlongint; find(1,1,n,j,n); f[j]:=sum[j]-j*100+s; if j*100<=m then f[j]:=max(f[j],m-j*100+sum[j]); t:=f[j] div 100; modify(1,1,n,f[j]-sum[j]); end; writeln(f[n]);end.
diffcultly:**
T10
https://jzoj.net/senior/#contest/show/2013/2
Problem
给定n个人的“上司“,求出一个排列,使得每个人的上司在其前面,且一个人的下司一样按照等级顺序排好.
Data constraint
对于20%的数据,有N ≤ 9;
对于40%的数据,有对于所有K,有K ≤ 2;
对于60%的数据,有N ≤ 100;
对于100%的数据,有T ≤ 10,N ≤ 1000,K ≤ N。
Solution
考试时没有发现其实这是一棵树.
这点首先得想到,以后遇到这类有关系的题目,且只有一个父亲的,一定要想到树.
然后再看一下数据范围和取模,hh,考虑一下dp吧.
这是一道质量比较高的树形Dp,也是我不擅长的题目类型.
我们先来看一下要怎么求方案数.
假设,当前一个父亲
因为这
而当有一个儿子有儿子时,我们就可以拿
那么怎么算才不会把方案数算重?
设
我们可以这么想,如果一个父亲
假设现在算
很显然,i个数插进j个空里可以用组合去求,实际上组合数是一个
虽然这个组合数很好码,但是要发现并不简单,其实可以用普通的排列组合先试一下,随后再推一推这个式子
那么这道题就可以很完美解决了.
由于是第一次做比较难的树形dp,花的时间比较多,也要多补补树形dPde知识,so interesting!
https://jzoj.net/senior/#main/show/3889 (位运算DP)
https://jzoj.net/senior/#main/show/1221(状压DP,学习lin大爷做法)
https://jzoj.net/senior/#main/show/3844(树形DP)
https://jzoj.net/senior/#main/show/3876(普通优化DP)
https://jzoj.net/senior/#main/show/3805(树形DP)
https://jzoj.net/senior/#main/show/3803(状压DP)
https://jzoj.net/senior/#main/show/4886(KMP嵌套DP)
https://jzoj.net/senior/#main/show/1771(单调和堆优化)
https://jzoj.net/senior/#main/show/3784(DP,不同方法)
https://jzoj.net/senior/#main/show/3792(DP,不同方法)
https://jzoj.net/senior/#main/show/1292(DP,不同方法)
https://jzoj.net/senior/#main/show/1293(DP,不同方法)
https://jzoj.net/senior/#main/show/1281(DP,不同方法)
https://jzoj.net/senior/#main/show/3046(DP,不同方法)
https://jzoj.net/senior/#main/show/3019(DP,不同方法)
https://jzoj.net/senior/#main/show/3034(树形DP)
https://jzoj.net/senior/#main/show/3056(DP,不同方法)
https://jzoj.net/senior/#main/show/3067(状压DP)
https://jzoj.net/senior/#main/show/1857(DP,不同方法)
https://jzoj.net/senior/#main/show/2545(DP,不同方法)
https://jzoj.net/senior/#main/show/2540(位运算优化DP)
https://jzoj.net/senior/#main/show/3095(区间DP)
https://jzoj.net/senior/#main/show/1265(数位DP)
https://jzoj.net/senior/#main/show/1210(DP,不同方法)
https://jzoj.net/junior/#main/show/1521(数位DP)
https://jzoj.net/junior/#contest/show/1373/3(DP,不同方法)
https://jzoj.net/senior/#contest/show/1929/2(DP,不同方法)
https://jzoj.net/senior/#contest/show/1916/3(新颖的区间DP)
单调优化,堆优化,这两种优化必须熟练掌握.
四边形不等式优化,斜率优化,需要了解透彻.
- 浅谈DP
- 浅谈数位DP
- 浅谈数位DP
- 浅谈单调队列优化dp
- 浅谈单调队列优化dp
- HDU2829 浅谈四边形优化DP
- POJ3093 浅谈背包DP预处理
- 浅谈 概率与期望 DP
- 数位DP 浅谈(hihocoder 1033:交错和)
- Android px、dp 和 sp 浅谈
- 数位DP 浅谈(hihocoder 1033:交错和)
- 浅谈dp 动态规划(1)
- 浅谈dp 动态规划(2)
- 数位DP 浅谈(hihocoder 1033:交错和)
- 浅谈安卓单位px,dp,sp
- 浅谈dp px ppi dpr dpi
- Android中dp,px,sp浅谈
- 浅谈数位DP的记忆化搜索
- WorkerMan学习篇:websocket+workerman聊天功能(二):同步在线用户列表
- PDO数据访问抽象层
- C++ Const深入解析
- |算法讨论|可并堆 学习笔记
- 简单了解sun.misc.Unsafe
- 浅谈DP
- Integer 类型与 int 的==比较
- bootstrap提供的h标签重置
- 【XML】 (4)元素与属性
- 3.4
- workerMan学习篇:websocket+workerman聊天功能(三):点对点发送消息模拟
- LeetCode 495 Teemo Attacking
- Fragment全解析系列(一):那些年踩过的坑
- 深入理解非阻塞同步IO和非阻塞异步IO