动态规划dp专题练习
貌似开坑还挺好玩的...开一个来玩玩=v=...
正好自己dp不是很熟悉,就开个坑来练练吧...先练个50题?小目标... 好像有点多啊QAQ
既然是开坑,之前写的都不要了!
50/50
1.洛谷P3399 丝绸之路 简单的线性dp
因为是开坑所以题意就不讲了,自己看题吧,一些题意比较迷的会讲一下。
这题其实还挺简单的。
设 f[i,j] 表示到第 i 个城市用了 j 天所需要的最小疲劳值。
很快dp方程就出来了。 f[i,j]=min(f[i,j-1],f[i-1,j-1]+d[i]*c[j]) 第一个是选择休息第二个是选择走。
初始化 f[i,i]=f[i-1,i-1]+a[i]*b[i] 一天都不休息就是天天走的值
var n,m:longint;
i,j:longint;
a,b:array[..]of longint;
f:array[..,..]of longint;
function min(a,b:longint):longint;
begin
if a<b then exit(a) else exit(b);
end;
begin
read(n,m);
for i:= to n do
read(a[i]);
for i:= to m do
read(b[i]);
for i:= to n do
begin
f[i,i]:=f[i-,i-]+a[i]*b[i];
for j:=i+ to m do
f[i,j]:=min(f[i,j-],f[i-,j-]+a[i]*b[j]);
end;
writeln(f[n,m]);
end.
丝绸之路
2.洛谷P1435 回文字串 神奇姿势 (有点套路)
这题我看完确实萌比了,想了还挺久的dp,却完全没有思路,回文串?不满足无后性啊,到底怎么dp...然后当然只能厚颜无耻的悄悄咪咪的去翻了题解十分努力的耗尽脑细胞的想。
哦~原来是这样...
这题的姿势不得不说十分巧妙。回文串,反过来一样!所以就可以把原串反一下。
然后?求最长公共子序列。为什么求最长公共子序列,因为回文串满足反过来是一样的,所以和反过串公共的部分就当做是回文串里原本有的,然后我萌就要加上辣些没有的,这个要加的值就是答案,要答案就小,就要子序列最长。这个姿势get!
关于最长公共子序列,设 f[i,j]为 s串中1-i
s1串中1-j 的最长公共子序列。
这样 分两种情况, s[i]=s1[j] : f[i,j]=f[i-1,j-1]+1
s[i]<>(!=)s1[j]:f[i,j]=max(f[i-1,j],f[i,j-1])
var s,s1:ansistring;
n:longint;
f:array[..,..]of longint;
i,j:longint;
function max(a,b:longint):longint;
begin
if a>b then exit(a) else exit(b);
end;
begin
readln(s);
n:=length(s);
for i:= to n do
s1:=s[i]+s1;
for i:= to n do
for j:= to n do
if s[i]=s1[j] then f[i,j]:=f[i-,j-]+ else
f[i,j]:=max(f[i-,j],f[i,j-]);
writeln(n-f[n,n]);
end.
回文字串
3.洛谷P1481 魔族密码 简单的线性dp (预处理+简单dp)
这题也比较简单。
dp[i] 表示以 i 结束的串最多能有多少。
很快就有dp方程
dp[i]=max(dp[j]+1) (1≤j≤i-1) (满足a[j] 为 a[i]的前缀)
由于是前缀,然后题目又保证了无重复的两个串,所以要先按长度这个关键字排下序,这样可以保证 i 之后一定没有 a[i] 的前缀 因为i之后的长度都为大于等于i的,而长度等于 i 不可能是a[i](无重复的两个串)。所以拍序后保证了dp的无后性。
这个方程去转移的话是三重的 效率是O(n*n*s) n只有2000 s只有75 所以三重去水是可以过的。
可以变成两重吗?当然可以,可以预处理每个字符串1-i 这个前缀的hash值,然后转移的时候直接判hash就好了。
这样的效率是O(n*n)砍掉了 s 当s很大的情况hash是比较必要的。
var
n:longint;
a:array[..]of string;
b:array[..]of longint;
i,j:longint;
dp:array[..]of longint;
ans:longint;
function ok(a,b:string):boolean;
var i:longint;
begin
for i:= to length(b) do
if a[i]<>b[i] then exit(false);
exit(true);
end;
function max(a,b:longint):longint;
begin
if a>b then exit(a) else exit(b);
end;
procedure qs(l,r:longint);
var i,j,m,t:longint;
s:string;
begin
i:=l;
j:=r;
m:=b[(l+r)>>];
repeat
while b[i]<m do inc(i);
while b[j]>m do dec(j);
if i<=j then
begin
t:=b[i];b[i]:=b[j];b[j]:=t;
s:=a[i];a[i]:=a[j];a[j]:=s;
inc(i);
dec(j);
end;
until i>j;
if l<j then qs(l,j);
if i<r then qs(i,r);
end; begin
readln(n);
for i:= to n do
begin
readln(a[i]);
b[i]:=length(a[i]);
dp[i]:=;
end;
qs(,n);
for i:= to n do
for j:= to i- do
if ok(a[i],a[j]) then
dp[i]:=max(dp[j]+,dp[i]);
for i:= to n do
ans:=max(ans,dp[i]);
writeln(ans);
end.
魔族密码 O(n*n*s)
const base=;
HR=;
var
n:longint;
a:array[..]of string;
b,id:array[..]of longint;
hash:array[..,..]of longint;
i,j:longint;
dp:array[..]of longint;
ans:longint;
function max(a,b:longint):longint;
begin
if a>b then exit(a) else exit(b);
end;
procedure qs(l,r:longint);
var i,j,m,t:longint;
s:string;
begin
i:=l;
j:=r;
m:=b[(l+r)>>];
repeat
while b[i]<m do inc(i);
while b[j]>m do dec(j);
if i<=j then
begin
t:=b[i];b[i]:=b[j];b[j]:=t;
t:=id[i];id[i]:=id[j];id[j]:=t;
s:=a[i];a[i]:=a[j];a[j]:=s;
inc(i);
dec(j);
end;
until i>j;
if l<j then qs(l,j);
if i<r then qs(i,r);
end; begin
readln(n);
for i:= to n do
begin
readln(a[i]);
b[i]:=length(a[i]);
for j:= to length(a[i]) do
hash[i,j]:=(base*hash[i,j-]+ord(a[i][j]))mod HR;
dp[i]:=;
id[i]:=i;
end;
qs(,n);
for i:= to n do
for j:= to i- do
if hash[id[i],b[j]]=hash[id[j],b[j]] then
dp[i]:=max(dp[j]+,dp[i]);
for i:= to n do
ans:=max(ans,dp[i]);
writeln(ans);
end.
hash O(n*n)
因为s比较小所以只写了1hash就能水过,s大一点的时候建议改成2hash甚至3hash。
4.洛谷P2062 分队问题 有点萌比的dp (难qaq)
这题的话是我无意在洛谷看到的,发现似曾相识,猛的记起是某次srm(某个有趣的自测赛)的题。
然后就去翻了一下,发现还真是(居然找到原题了2333)...当时这题打了贪心挂的很惨,正解dp十分短小精悍
但是理解了很久还是不太懂(是的,我看题解了)。
大致是用一个 f[i] 表示 1 到 i 中 选了 i 后的最优解。
然后用一个 g[i] 表示 f[1]..f[i-1] 中的最大值。
这样的话对于 f[i] 可以这样转移 f[i]=g[i-a[i]]+1 大致我理解成了,可以通过得到的之前的最优解的最大值来转移,保证了这个更新答案也会是一个最优解。
至于 i-a[i] 就是在选了 i 这个元素后保证i 需要a[i]个 所以必须在 1 到 i-a[i] 区间进行选择,然后又因为用g[i]已经保存了这个最大值,所以直接用g[i-a[i]] 更新。
然后对于 g的更新就是 g[i]=max(f[i],g[i-1]) 这个就可以保存这个最大值了。
QAQ还是太弱了,不是很理解。
var n,i:longint;
f,g,a:array[..]of longint;
function max(a,b:longint):longint;
begin
if a>b then exit(a) else exit(b);
end;
procedure qs(l,r:longint);
var i,j,m,t:longint;
begin
i:=l;
j:=r;
m:=a[(l+r)>>];
repeat
while a[i]<m do inc(i);
while a[j]>m do dec(j);
if i<=j then
begin
t:=a[i];a[i]:=a[j];a[j]:=t;
inc(i);
dec(j);
end;
until i>j;
if l<j then qs(l,j);
if i<r then qs(i,r);
end;
begin
read(n);
for i:= to n do
read(a[i]);
qs(,n);
for i:= to n do
begin
if i>=a[i] then f[i]:=g[i-a[i]]+; //注意 i-a[i] 不能小于0
g[i]:=max(f[i],g[i-]);
end;
writeln(f[n]);
end.
分队问题
5.校内胡测...
题意是 n (5000)个 坐标xi(互不相同),有两个棋子,可以初始放在这n个坐标当中。如果满足 i,j有棋子,且在 n个坐标中有 k 坐标 且满足 k-j=j-i 辣么 i 可以跳到 k位置,问最多可以跳几次。
这题应该是n^2 效率的我写了n^2logn(貌似是n^2...QAQ)。
设 f[i,j] 表示 第一个棋子在 xi 位置,第二个棋子在 xj 位置 结束,跳的步数最大值。 (xi<xj)
f[j,k]=max(f[i,j]+1,f[j,k]) 满足 xk-xj=xj-xi
答案就是max(f[i,j]) (1≤i≤n-1, i<j≤n)
这是一个n^3的dp显然有个优化,确定了 i,j xk就可以算出来 而k可以二分查找。 (先排序使xi 有序)
显然优化到了 n^2logn
但还有一个优化,对于 i 不变 j 增大, k必然增大。 所以每次二分的区间也可以缩小。
然后就这样被我奇奇怪怪的写法卡AC了=v=
var i,j,k,x:longint;
n:longint;
f:array[..,..]of longint;
a:array[..]of longint;
ans:longint;
last:longint;
procedure qs(l,r:longint);
var i,j,m:longint;
t:longint;
begin
i:=l;
j:=r;
m:=a[(l+r)>>];
repeat
while a[i]<m do inc(i);
while a[j]>m do dec(j);
if i<=j then
begin
t:=a[i];a[i]:=a[j];a[j]:=t;
inc(i);
dec(j);
end;
until i>j;
if l<j then qs(l,j);
if i<r then qs(i,r);
end;
function max(a,b:longint):longint;
begin
if a>b then exit(a) else exit(b);
end;
function find(x:longint):longint;
var l,r,m:longint;
begin
l:=last;
r:=n;
while l<r do
begin
m:=(l+r+)>>;
if a[m]<=x then l:=m else r:=m-;
end;
if a[l]=x then exit(l) else exit();
end;
begin
read(n);
for i:= to n do
read(a[i]);
qs(,n);
for i:= to n- do
begin
last:=i;
for j:=i+ to n do
begin
x:=a[j]*-a[i];
k:=find(x);
if k<> then
begin
f[j,k]:=max(f[i,j]+,f[j,k]);
ans:=max(f[j,k],ans);
end;
last:=k;
end;
end;
writeln(ans);
end.
校内胡测
6.洛谷P2066 机器分配 简单线性dp+倒推姿势 (dp一般,倒推麻烦)
这题也是比较简单...
设 f[i,j] 表示 前 i 个公司总共用了 j 台机器的最优解。 辣答案就是 f[n,m]
辣么方程很好写 f[i,j]=max(f[i-1,k]+a[i,j-k]) (0≤k≤j)
枚举一个 k 表示 前 i-1 个公司用了 k 台 则第 i 个公司就要有 j-k 台
至于方案的输出,可以用倒推的方法从 n,m 推回去 由于要字典序最小,所以倒推的时候就取字典序最大。
var n,m:longint;
i,j,k:longint;
a,f:Array[..,..]of longint;
now,need:longint;
ans:array[..]of longint;
function max(a,b:longint):longint;
begin
if a>b then exit(a) else exit(b);
end;
begin
read(n,m);
for i:= to n do
begin
for j:= to m do
read(a[i,j]);
readln;
end;
for i:= to n do
for j:= to m do
for k:= to j do
f[i,j]:=max(f[i,j],f[i-,k]+a[i,j-k]);
writeln(f[n,m]);
now:=n;
repeat
need:=;
for i:= to m do
if f[now,m]=f[now-,i]+a[now,m-i] then
if need<m-i then need:=m-i;
ans[now]:=need;
dec(m,need);
dec(now);
until now=;
for i:= to n do
writeln(i,' ',ans[i]);
end.
洛谷P2066
7.洛谷P1977 出租车拼车 简单线性dp (基础dp)
这题的话和上一题似乎一样QAQ...随便找的题居然找到两个相同的...
和上一题一样 设 f[i,j] 表示 前 i 辆车载了 j 个oier的最优解。辣答案就是 f[k,n]
方程一样的QAQ f[i,j]=min(f[i-1,j-x]+x*t[i]+d) (1≤x≤min(z[i],j))
f[i,j]=f[i-1,j] (x=0) 如果没有人上车,就不需要付 D 元
然后初值是注意的点 f[0,j]=∞ (1≤j≤n)(第0辆车载任何乘客都是无穷大的价值,因为载不了)
var n,m,d,s:longint;
f:array[..,..]of longint;
t,z:array[..]of longint;
i,j,k:longint;
sum:longint;
function min(a,b:longint):longint;
begin
if a<b then exit(a) else exit(b);
end;
begin
read(n,m,d,s);
for i:= to m do
begin
read(t[i],z[i]);
inc(sum,z[i]);
end;
if sum<n then
begin
writeln('impossible');
exit;
end;
for i:= to n do
f[,i]:= << ;
for i:= to m do
for j:= to n do
begin
f[i,j]:=maxlongint;
for k:= to min(z[i],j) do
if k= then f[i,j]:=f[i-,j] else
f[i,j]:=min(f[i,j],f[i-,j-k]+k*t[i]+d);
end;
writeln(f[m,n]);
end.
洛谷P1977
上面的代码似乎有点问题...
以上dp没有问题,思考一下另一个dp
f[i][j] 表示 前 i 辆车载了 j 个oier的最优解。辣答案就是 f[k,n]
换一个想法,对于代价,思考那些剩下的人所停留的代价
f[i][j]=min(f[i-1][j-x]+(n-(j-x))*(t[i]-t[i-1]))+d (1<=x<=min(z[i],j))
f[i][j]=f[i-1][j]+(n-j)*(t[i]-t[i-1]) (x=0)
var n,m,d,s:longint;
f:array[..,..]of longint;
t,z:array[..]of longint;
i,j,k:longint;
sum:longint;
function min(a,b:longint):longint;
begin
if a<b then exit(a) else exit(b);
end;
begin
read(n,m,d,s);
for i:= to m do
begin
read(t[i],z[i]);
inc(sum,z[i]);
end;
if sum<n then
begin
writeln('impossible');
exit;
end;
for i:= to n do
f[,i]:= << ;
for i:= to m do
for j:= to n do
begin
f[i,j]:=maxlongint;
for k:= to min(z[i],j) do
if k= then f[i,j]:=f[i-,j]+(n-j)*(t[i]-t[i-]) else
f[i,j]:=min(f[i,j],f[i-,j-k]+(n-j+k)*(t[i]-t[i-])+d);
end;
writeln(f[m,n]);
end.
NEW
8.洛谷P1103 书本整理 简单线性dp(基础dp)
设 f[i,j] 表示 前 i 本书选 j 本书且以i 结束的最优值。 辣答案就是 min(f[i,n-k]) (n-m≤i≤n)
方程: f[i,j]=min(f[x,j-1])+abs(a[x].w-a[i].w) (1≤k≤i-1) (2≤j≤i)
f[i,j]=0 (j=1)
type
node=record
w,h:longint;
end;
var i,j,k:longint;
a:array[..]of node;
n,m:longint;
f:array[..,..]of longint;
ans:longint;
procedure qs(l,r:longint);
var i,j,m:longint;
t:node;
begin
i:=l;
j:=r;
m:=a[(l+r)>>].h;
repeat
while a[i].h<m do inc(i);
while a[j].h>m do dec(j);
if i<=j then
begin
t:=a[i];a[i]:=a[j];a[j]:=t;
inc(i);
dec(j);
end;
until i>j;
if l<j then qs(l,j);
if i<r then qs(i,r);
end;
function min(a,b:longint):longint;
begin
if a<b then exit(a) else exit(b);
end;
begin
read(n,m);
for i:= to n do
read(a[i].h,a[i].w);
qs(,n);
for i:= to n do
begin
f[i,]:=;
for j:= to i do
begin
f[i,j]:=maxlongint;
for k:= to i- do
begin
if k>=j- then f[i,j]:=min(f[k,j-]+abs(a[k].w-a[i].w),f[i,j]);
end;
end;
end;
ans:=maxlongint;
for i:=n-m to n do
if f[i,n-m]<ans then ans:=f[i,n-m];
writeln(ans);
end.
洛谷P1103
9.洛谷P1279 字串距离 有趣dp(注意初始化)
这题的话QAQ有点奇奇怪怪QAQ,想了一会大致可做。然后悄悄咪咪看了题解。
发现题解和自己写的差不多的dp QAQ。
设 f[i,j] 表示 A串前 i 个字符,B串前 j 个字符的最小距离。辣答案就是 f[lena,lenb]。
方程: f[i,j]=min(f[i-1,j]+k,f[i,j-1]+k,f[i-1,j-1]+abs(a[i]-b[j]))
分三类考虑 ①第 i 位为空格与第 j 位
②第 j 位为空格与第 i 位
③第 i 位与第 j 位
然后我就成功挂掉了QAQ 又悄悄咪咪打开题解
没考虑初始化。
f[i,0]=f[i-1,0]+k B串无字符,就必须用 i 个空格。
f[0,i]=f[0,i-1]+k 同理
QAQ初始化要多考虑。
var
a,b:ansistring;
lena,lenb:longint;
i,j:longint;
k:longint;
f:array[..,..]of int64;
function min(a,b:longint):longint;
begin
if a<b then exit(a) else exit(b);
end;
begin
readln(a);
lena:=length(a);
readln(b);
lenb:=length(b);
readln(k);
for i:= to lena do
f[i,]:=f[i-,]+k;
for i:= to lenb do
f[,i]:=f[,i-]+k;
for i:= to lena do
begin
for j:= to lenb do
begin
f[i,j]:=maxlongint;
f[i,j]:=min(f[i-,j]+k,f[i,j]);
f[i,j]:=min(f[i,j-]+k,f[i,j]);
f[i,j]:=min(f[i-,j-]+abs(ord(a[i])-ord(b[j])),f[i,j]);
end;
end;
writeln(f[lena,lenb]);
end.
洛谷P1279
10.洛谷P2308 添加括号 玄学区间dp+乱搞姿势 (倒推麻烦)
这题也是奇奇怪怪QAQ。
第二问就是最基本的石子归并 设 f[i,j] 表示 i 到 j 中合并后的最小值。辣答案就是 f[1,n]
预处理一个数组 sum[i,j] 表示合并 i 到 j 后的值是多少 sum[i,j]=sum[i,j-1]+a[j]
f[i,j]=min(f[i,k]+f[k+1,j]+sum[i,j])
重点就是第一问和第三问。
对于这两问可以一起求。
用一个 dfs 从[1,n] 这个状态往回推每次找到一个 x 表示 当前 [l,r] 状态是用 [l,x] 和 [x+1,r] 转移而来的。
然后对于第一问,可以这样考虑,记录一个 numL[i] 表示 i 作为 l 被递归了几次。由题目的观察可以发现,i 作为 l 被递归的次数(除 l=r=i的情况时) 就是 i 前面(与 i 相连)的左括号数。
类似的,记录一个numR[i] 表示 i 作为 r 被递归了几次。还是由题目的观察可以发现,i 作为 r 被递归的次数(除 l=r=i的情况时) 就是 i 后面的(与 i 相连)右括号数。
当然 如果numL[i]≠0 则 numR[i]=0 因为除去了 l=r=i 的情况时。
然后在输出的时候 对于每一个数前面输出 numL[i] 个‘(’ 后面输出numR[i] 个‘)’ 然后在两个数之间加 ‘+’字符即可。
对于第二问,由于是dfs将[1,n]状态进行分割,所以就是在回溯时,直接记录 sum[l,r] 就好辣。
然后就愉快的AC了
var n:longint;
i,j,k:longint;
f,sum:array[..,..]of longint;
a,ans,numl,numr:array[..]of longint;
orzn:array[..]of string;
//rp++,存有 numL[i] 个'(' 或 numR[i] 个')'
zn:longint;
ansl,ansr:longint;
function min(a,b:longint):longint;
begin
if a<b then exit(a) else exit(b);
end;
procedure dfs(l,r:longint);
var k,x:longint;
s1:string;
begin
if l=r then exit;
// l=r 的情况就先exit掉,这样不会统计到numL,numR中
inc(numl[l]);
inc(numr[r]);
for k:=l to r- do
if f[l,r]=f[l,k]+f[k+,r]+sum[l,r] then x:=k;
dfs(l,x);
dfs(x+,r);
inc(zn);
ans[zn]:=sum[l,r];
end;
begin
read(n);
for i:= to n do
read(a[i]);
for i:= to n do
begin
f[i,i]:=;
sum[i,i]:=a[i];
for j:=i+ to n do
sum[i,j]:=sum[i,j-]+a[j];
end;
for i:=n downto do
for j:=i+ to n do
begin
f[i,j]:=maxlongint;
for k:=i to j- do
f[i,j]:=min(f[i,k]+f[k+,j]+sum[i,j],f[i,j]);
end;
dfs(,n);
for i:= to n do
if numl[i]<> then
begin
for j:= to numl[i] do
orzn[i]:=orzn[i]+'(';
end else
begin
for j:= to numr[i] do
orzn[i]:=orzn[i]+')';
end;
for i:= to n do
if numl[i]<> then write(orzn[i],a[i],'+') else
begin
if i=n then writeln(a[i],orzn[i]) else write(a[i],orzn[i],'+');
end;
writeln(f[,n]);
for i:= to zn do
write(ans[i],' ');
writeln;
end.
洛谷P2308
11.洛谷P1140 相似基因 和第9题一样QAQ
设 f[i,j] 表示 A串前 i 个字符,B串前 j 个字符的最大相似度。辣答案就是 f[lena,lenb]。
方程: f[i,j]=max(f[i-1,j]+cost('-',a[i]),f[i,j-1]+cost('-',b[j]),f[i-1,j-1]+cost(a[i],b[j]))
分三类考虑 ①第 i 位为‘-’与第 j 位
②第 j 位为‘-’与第 i 位
③第 i 位与第 j 位
初始化一样的...基本就是等于第9题辣个字串距离。
就是距离打个表就好了...
const
c:array[''..'',''..'']of longint=((,-,-,-,-),
(-,,-,-,-),
(-,-,,-,-),
(-,-,-,,-),
(-,-,-,-,));
var n,m:longint;
a,b:string;
i,j:longint;
f:array[..,..]of longint;
function max(a,b:longint):longint;
begin
if a>b then exit(a) else exit(b);
end;
begin
read(n);
readln(a);
read(m);
readln(b);
delete(a,,);
delete(b,,);
for i:= to n do
begin
if a[i]='A' then a[i]:='' else
if a[i]='C' then a[i]:='' else
if a[i]='G' then a[i]:='' else
if a[i]='T' then a[i]:='';
f[i,]:=f[i-,]+c[a[i],''];
end;
for i:= to m do
begin
if b[i]='A' then b[i]:='' else
if b[i]='C' then b[i]:='' else
if b[i]='G' then b[i]:='' else
if b[i]='T' then b[i]:='';
f[,i]:=f[,i-]+c[b[i],''];
end;
for i:= to n do
for j:= to m do
begin
f[i,j]:=-maxlongint;
f[i,j]:=max(f[i-,j]+c['',a[i]],f[i,j]);
f[i,j]:=max(f[i,j-]+c['',b[j]],f[i,j]);
f[i,j]:=max(f[i-,j-]+c[a[i],b[j]],f[i,j]);
end;
writeln(f[n,m]);
end.
洛谷P1140
12.codevs 1184 瓷砖覆盖 状压dp入门 (入门状压)
QAQ最近既然开了dp坑,就顺便把状压一起学了吧唔...
看了很久题解博客大概知道一点状压了吧QAQ
设 f[i,j] 表示 铺到第i层 前 i-1层都已经铺满,且 当前层状态为 j 的方案数。
状态指: 如果这一层中的格子铺了,辣么为1 否则为0。然后这样的一个01串转为十进制后的数。
对于 f[i,j]<>0 的情况(有这样的状态再去转移下一层的): 可以去转移到 i+1 层,又为了保证 前 i-1 层都已经铺满这样的条件。所以 前 i+1-1 层 都要铺满。
辣么对于第 i 层的所以可行 j 状态(f[i,j]<>0) dfs的去找转移到下一层时,下一层的状态。
然后 f[i+1,news]+=f[i,j] 进行转移。
const HR=;
var f:array[..,..]of int64;
n,m:longint;
i,j:longint;
procedure dfs(x:longint;nows,news:int64);
// 铺到第 x 个格子。当前行状态为nows 下一行状态为news
begin
if x=m+ then //第 i 行铺满了就进行转移了。
begin
inc(f[i+,news],f[i,nows]);
if f[i+,news]>=HR then f[i+,news]:=f[i+,news] mod HR;
//膜神犇rp++ 常数--
exit;
end;
// shl (x-) and nows 是判断 nows 的第 x 格是否是1 如果是1则>
// 对于 x 格是1的情况不需要铺
if ( shl (x-)) and nows> then dfs(x+,nows,news) else
begin
if (( shl x) and nows=)and(x+<=m) then dfs(x+,nows,news);
// 对于 x+ 格也是0 时,可以铺横的。对下一行无影响
dfs(x+,nows,news or ( shl (x-)));
// 铺竖的,对下一行的影响就是 把 x 这个位置变成
// news or ( shl (x-)) 就是 对news 的第 x 格置1的操作
end;
end;
begin
readln(m,n);
//读入m,n 而不读入n,m是因为m过于大 <<m会爆掉
//而且2^m效率是会挂的。
f[,]:=; //第 层什么都不铺的方案为1
for i:= to n do //枚举铺的每一层
for j:= to ( shl m) - do //枚举 i 层的所有状态
if f[i,j]<> then dfs(,j,); //如果是可行状态就对下一层进行转移
writeln(f[n+,]);
end.
codevs1184
对于 f 数组 还可以滚动数组优化。我懒得写了=v=
13.洛谷P1879 [USACO06NOV]玉米田Corn Fields 状压dp入门题
唔...这题的话也是一道比较简单的状压dp...随便乱想的,居然一次过啦=v= (虽然瞄了一眼题解,但是没细看)
设 f[i,j] 表示 第 i 行 且 第 i 行的状态为 j 时的方案数。 辣么答案就是 sum(f[n,j]) j 是 对于第 n 行来说可行的状态
辣么方程应该是 f[i,j]=sum(f[i-1,k]) 只要枚举一个 k 状态表示上一行的状态,再判这个状态是否合法,就行辣!=v=
对于一个状态 x ,合法就要满足不存在任意两个相邻格子都为1且 不存在一个格子为1但原地图为0 。可以O(m) 判一下。
对于两个状态 x (当前行)和 y(上一行) ,合法就要满足不存在 任意两个同列格子都为1。一样O(m)判一下。
这样的话理论效率为 O(n*2m*2m*m) 看起来似乎是卡着过的,但是实际上的时间却很快,原因应该是一个小剪枝。
如果对于当前行的 j 状态已经不合法 就没必要再去枚举 k。 而合法的 j 其实不多,所以实际上效率是很快哒=v=
const HR=; var n,m:longint;
i,j,k:longint;
ans:longint;
f:array[..,..]of longint;
a:array[..,..]of longint;
function isnot(i,x:longint):boolean;
begin
exit( << (i-) and x>);
end;
function ok(c,x:longint):boolean;
var i:longint;
begin
for i:= to m do
begin
if (isnot(i,x))and(a[c,i]=) then exit(false);
if (i>)and(isnot(i,x))and(( << (i-)) and x>) then exit(false);
end;
exit(true);
end;
function check(x,y:longint):boolean;
var i:longint;
begin
for i:= to m do
if isnot(i,x)and isnot(i,y) then exit(false);
exit(true);
end;
begin
read(n,m);
for i:= to n do
begin
for j:= to m do
read(a[i,j]);
readln;
end;
f[,]:=;
for i:= to n do
for j:= to ( << m)- do
if ok(i,j) then
begin
for k:= to ( << m)- do
if ok(i-,k) and check(j,k) then
begin
inc(f[i,j],f[i-,k]);
if f[i,j]>=HR then f[i,j]:=f[i,j] mod HR;
end;
if i=n then
begin
inc(ans,f[i,j]);
if ans>=HR then ans:=ans mod HR;
end;
end;
writeln(ans);
end.
洛谷1879
大致都已经很明白了就不注释了QAQ 懒兔纸一只
14.bzoj1087(codevs2451) 互不侵犯King 状压dp
这题写了个题解,毕竟是bzoj的可以水博客
15.bzoj1879 [SDOI2009]Bill的挑战 状压dp
估计是bzoj的题都会单独写一篇。
15题辣!=v=
还有35题QAQ好漫长...
16.洛谷P1336 最佳课题选择 (一般)
这题的话,也不会很难...
设 f[i,j] 表示用前 i 中课题共写了 j 篇论文的最优解 答案就是 f [m,n]
初始化为 f [0,i]=∞ (1≤i≤n) 不用课题就想完成论文就是无穷大
f[0,0]=0 不用课题,不完成论文的最优解就是 0
方程 f[i,j]=min(f[i-1,j-x]+a[i]*xb[i]) (枚举 x 表示 第 i 个课题写x篇)
幂我用快速幂算了...暴力应该也是可以的
注意一下long long
var n,m:longint;
a,b:array[..]of longint;
f:array[..,..]of int64;
i,j,x:longint;
function min(a,b:int64):int64;
begin
if a<b then exit(a) else exit(b);
end;
function ksm(a,b:int64):int64;
var t,y:int64;
begin
t:=;
y:=a;
while b<> do
begin
if b and = then t:=t*y;
y:=y*y;
b:=b shr ;
end;
exit(t);
end;
begin
read(n,m);
for i:= to m do
read(a[i],b[i]);
for i:= to n do
f[,i]:=maxlongint;
for i:= to m do
for j:= to n do
begin
f[i,j]:=maxlongint;
for x:= to j do
f[i,j]:=min(f[i,j],f[i-,j-x]+a[i]*ksm(x,b[i]));
end;
writeln(f[m,n]);
end.
luogu P1336
17.洛谷P2029 跳舞 乱写dp (一般)
设 f[i,j] 表示 到第 i 个 前面已经踩了 j 个 的最优解 答案就是 max(f[n,i]) (1≤i≤n)
方程 f[i,j]=max(f[i-1,j]-s[i],f[i-1,j-1]+s[i]+b[i]) (j mod t=0的情况,可以选择不踩,就是 -s[i] 还有就是踩 +s[i]+b[i])
同样 f[i,j]=max(f[i-,j]-s[i],f[i-1,j-1]+s[i]) (j mod t≠0的情况balalala)
初始化为 f[i,0]=f[i-1,0]-s[i] 一个都不踩就都要扣掉
f[0,0]=0
var i,j:longint;
s,b:array[..]of longint;
f:array[..,..]of longint;
n,t,ans:longint;
function max(a,b:longint):longint;
begin
if a>b then exit(a) else exit(b);
end;
begin
read(n,t);
for i:= to n do
read(s[i]);
for i:= to n do
read(b[i]);
for i:= to n do
begin
f[i,]:=f[i-,]-s[i];
for j:= to i do
if j mod t= then f[i,j]:=max(f[i-,j]-s[i],f[i-,j-]+s[i]+b[i]) else
f[i,j]:=max(f[i-,j]-s[i],f[i-,j-]+s[i]);
end;
for i:= to n do
ans:=max(ans,f[n,i]);
writeln(ans);
end.
luoguP2029
18.洛谷P2704 炮兵阵地 状压dp
这个题很像第14题,就是加强了一下,改了个方向和地形的限制而已
因为关系到了两行,所以把方程改成这样
f[i,j,x] 表示 前 i 行 第i行为 j 状态 第 i-1 行为 x 状态的最优解
然后枚举一个 y 表示 第i-2 行的状态,然后暴力判 x,y,j是否合法
如果合法就转移 f[i,j,x]=max(f[i,x,y]+j状态中的1个数)
显然O(n*2m*2m*2m)是会T的不要不要的
想想优化?当然是想到剪枝预处理。
预处理 num[i,j] 表示 第 i 行为 j 状态是否合法,如果合法就为 j状态中 1 的个数,如果不合法,就为-1
这样会剪掉很多很多的枚举范围,但还是会T (懒兔纸懒得再写优化所以交完T后不得不去改)
还有一个优化就是 要判 任意两行拼起来后是否合法,这个也会浪费很多时间,所以预处理
can[i,j] 表示 i 状态和 j 状态拼起来后是否合法 若合法就 true 不合法就是 false
这样就又减掉了重复的判断时间,然后就愉快的AC辣
var n,m:longint;
x,y,i,j:longint;
num:array[-..,..]of longint;
can:array[..,..]of boolean;
f:array[-..,..,..]of longint;
ans:longint;
a:array[..,..]of char;
function isnot(x,i:longint):boolean;
begin
exit(x and ( << (i-))>);
end;
function ok(thei,x:longint):longint;
var i,s:longint;
begin
s:=;
for i:= to m do
begin
if isnot(x,i) then inc(s);
if (a[thei,i]='H')and(isnot(x,i)) then exit(-);
if (i>=)and(isnot(x,i)and isnot(x,i-)) then exit(-);
if (i>=)and(isnot(x,i)and isnot(x,i-)) then exit(-);
end;
exit(s);
end;
function check(a,b:longint):boolean;
var i:longint;
begin
for i:= to m do
if isnot(a,i)and isnot(b,i) then exit(false);
exit(true);
end;
function max(a,b:longint):longint;
begin
if a>b then exit(a) else exit(b);
end;
begin
readln(n,m);
for i:= to n do
begin
for j:= to m do
read(a[i,j]);
readln;
end;
for i:= to n do
for j:= to ( << m)- do
num[i,j]:=ok(i,j);
for j:= to ( << m)- do
begin
num[,j]:=-;
num[-,j]:=-;
end;
for i:= to ( << m)- do
for j:= to ( << m)- do
can[i,j]:=check(i,j);
for i:= to n do
for j:= to ( << m)- do
if num[i,j]>= then
begin
for x:= to ( << m)- do
if (num[i-,x]>=)and(can[j,x]) then
begin
for y:= to ( << m)- do
if (num[i-,y]>=)and(can[j,y])and(can[x,y]) then
f[i,j,x]:=max(f[i-,x,y]+num[i,j],f[i,j,x]);
if i=n then ans:=max(f[i,j,x],ans);
end;
end;
writeln(ans);
end.
洛谷P2704 炮兵阵地
19.洛谷P2915 [USACO08NOV]奶牛混合起来Mixed Up Cows 状压dp
嗯...设 f[i,j] 表示 状态为 j 时 放在最后的牛是 i 的方案数 辣么答案就是 sum(f[i,1 << n-1]) (1≤i≤n)
这样的话方程就是
f[i,j or (1 << (i-1))]+=f[k,j]
这样的话会发现 i 和 k 的先后求的顺序是不同的,所以要改一下枚举的顺序 先枚举 j 再枚举 i,k
嗯...比较简单吧
var n,m:longint;
a:array[..]of longint;
f:array[..,..]of int64;
i,j,k:longint;
ans:int64;
function ok(thei,x:longint):boolean;
var i:longint;
begin
exit(x and ( << (thei-))=);
end;
begin
read(n,m);
for i:= to n do
read(a[i]);
for i:= to n do
f[i,( << (i-))]:=;
for j:= to ( << n)- do
for i:= to n do
if ok(i,j) then
begin
for k:= to n do
if not ok(k,j) then
if abs(a[k]-a[i])>m then inc(f[i,j or ( << (i-))],f[k,j]);
end;
for i:= to n do
inc(ans,f[i,( << n)-]);
writeln(ans);
end.
洛谷P2915
20.P1273 有线电视网 树形dp (较难)
嗯...又是看了题解的QAQ
设 dp[i,j] 表示 以 i 为根的子树中共选了 j 个叶子节点后剩下的最大值。
辣么答案就是 max(i) (1≤i≤n 且 dp[1,i]≥0)
dp[x,j]=max(dp[x,j],dp[x,j-k]+dp[now,k]-e[i].z) now为x的儿子,这个方程就是 枚举k 表示now这个儿子的子树中选了k个子节点。
由于每一个叶子节点是只能被选到一次的,和01背包相似,所以 j 要倒着枚举。
初值 所有的叶子节点x 的 dp[x,1] 为用户有的钱。
这样是一个 n^3 的dp,会TLE (50分) 想想优化。
对于 x 节点,实际上 j 不需要枚举到m这么大,只要枚举到sum[x] (sum[x] 表示 以x为根的子树中叶子节点的个数)
这样可以优化很多无效枚举,然后就100辣。
type
node=record
y,z:longint;
next:longint;
end;
var n,m:longint;
x,z,k,ans:longint;
i,j:longint;
dp:Array[..,..]of longint;
e:array[..]of node;
tot:longint;
first,sum:array[..]of longint;
function max(a,b:longint):longint;
begin
if a>b then exit(a) else exit(b);
end;
function min(a,b:longint):longint;
begin
if a<b then exit(a) else exit(b);
end;
procedure adde(x,y,z:longint);
begin
e[tot].next:=first[x];
e[tot].y:=y;
e[tot].z:=z;
first[x]:=tot;
inc(tot);
end;
procedure dfs(x:longint);
var i,j,k,now:longint;
begin
i:=first[x];
while i<>- do
begin
now:=e[i].y;
dfs(now);
i:=e[i].next;
inc(sum[x],sum[now]);
end;
i:=first[x];
while i<>- do
begin
now:=e[i].y;
for j:=sum[x] downto do
for k:= to min(sum[now],j) do
dp[x,j]:=max(dp[x,j],dp[x,j-k]+dp[now,k]-e[i].z);
i:=e[i].next;
end;
end;
begin
read(n,m);
for i:= to n do
first[i]:=-;
for i:= to n-m do
begin
read(k);
for j:= to k do
begin
read(x,z);
adde(i,x,z);
end;
end;
for i:= to n do
for j:= to m do
dp[i,j]:=- << ;
for i:=n-m+ to n do
begin
read(dp[i,]);
sum[i]:=;
end;
dfs();
for i:= to m do
if dp[,i]>= then ans:=i;
writeln(ans);
end.
洛谷P1273
惨啊QAQ都一个多月了,还没到一半耶QAQ
由于noip,原本打算noip前就填好的,结果dp的一些好题不是很好找(主要是自己不会QAQ再加上初三老年菜兔时间比较少了)
所以捏,估计这个坑先空一会,先转战noip,然后如果有刷到就加一点进来,所以不再局限于只刷dp题了。
啊哈~我回来啦!每次登博客看到这个顶置,不存在的良心都会很疼qwq 弃太久了不行啊...还是得回来补掉,不然老年菜兔的实力实在太菜了...
唔...不过这个坑是真的难填qwq...所以估计很简单的dp也放进来吧...随便凑个数了...因为最近都在补cf 所以题估计会放大部分cf的...cf的dp题遇到的不多...
所以填坑的道路十分漫长qwq (突然发现是有分割线这种东西的...我之前都手动分割线...似乎显得很傻..._(:з」∠)_)
坑的格式改一下吧...点我看题这样的东西好像很奇奇怪怪,直接搞题目上不就好了qwq 蠢到极点的兔纸...
21.cf910A 入门dp (最简单)
直接二重对可以到达的地方更新一下就好啦
dp[j]=min(dp[i]+1,dp[j]) (s[j]="1")
初始值就是dp[1]=0 dp[i]=∞ (2≤i≤n)
var n,d:longint;
i,j:longint;
s:string;
dp:array[..]of longint;
function min(a,b:longint):longint;
begin
if a<b then exit(a) else exit(b);
end;
begin
readln(n,d);
readln(s);
for i:= to n do
dp[i]:= << ;
dp[]:=;
for i:= to n- do
for j:=i+ to min(i+d,n) do
if s[j]='' then dp[j]:=min(dp[i]+,dp[j]);
if dp[n]= << then dp[n]:=-;
writeln(dp[n]);
end.
cf910A
22.cf909C 比较灵活的dp+前缀和优化 (难)
这题完全不懂怎么写dp...最后看题解了...理解了一番,大致懂了...
dp[i,j]表示到第 i 行代码,这行代码用了 j 个缩进
可以发现 j 最多为 n-1
初始 : dp[1,0]=1;
第一行代码必须是缩进为0的 方案为 1
缩进的意思为: 如上图 ①代码的缩进是 0
②代码的缩进是 1
③代码的缩进是 1
④代码的缩进是 2
那答案就为 sum(dp[n,i]) (0≤i≤ n-1)
考虑转移 如果第 i 条代码是 “f” 那下一条代码必须缩进 所以 dp[i+1,j]=dp[i,j-1]
如果第 i 条代码是 “s” 那下一条代码可以是 ≤i 缩进的 任意缩进
这个情况下 我萌设 i+1的缩进为 j i 的缩进为 k 则满足(0≤j≤k≤n-1) 所以 dp[i+1,j]+=dp[i,k]
整理得: dp[i+1,j]=dp[i,j-1] (s[i]="f")
dp[i+1,j]+=dp[i,k] (s[i]="s")
转移的时候枚举 i 和 j
对于 k (k≥j) 若枚举 就要 O(N3) 会超时。
发现 dp[i,k]是可以前缀和先预处理的 (当然后缀和也可以,而且更方便,但是我写的时候写了前缀和啊啊啊啊qwq,就懒的改代码了)
这样就优化到 O(N2) 于是不愉快的AC了
哇的一声哭出来QAQ dp...好...好难啊QAQ
Const HR=;
var n,ans,num:int64;
i,j,k:longint;
c:array[..]of char;
dp:array[..,..]of int64;
sum:array[..]of int64;
begin
readln(n);
for i:= to n do
readln(c[i]);
dp[,]:=;
for i:= to n do
begin
sum[]:=dp[i,];
for j:= to n- do
sum[j]:=(sum[j-]+dp[i,j])mod HR;
for j:= to n- do
begin
if c[i]='f' then
begin
if j> then dp[i+,j]:=dp[i,j-];
end else
begin
num:=sum[n-];
if j> then num:=(num+HR-sum[j-]) mod HR;
//注意因为我是前缀和,需要减,所以mod就要改成 +HR 后再mod
inc(dp[i+,j],num);
if dp[i+,j]>=HR then dp[i+,j]:=dp[i+,j] mod HR;
end;
end;
end;
writeln(sum[n-]);
end.
cf909C
23.洛谷 P1164 小A点菜 入门背包dp (入门背包)
设 f[i,j] 表示前 i 个菜花 j 元的方案数
则答案为 f[n,m]
初值 f[0,0]=1
对于每一个菜能选或者不选
所以 f[i,j]=f[i-1,j-a[i]]+f[i-1,j] (前为选的方案,后为不选的方案)
这样已经可以AC,考虑空间优化,由于 方程都是只和 i-1 有关的,所以考虑滚动数组,背包那样优化。
则 设 f[j] 表示花j元的方案数
f[j]+=f[j-a[i]]
注意是01背包所以 j 是倒着的
var n,m:longint;
i,j:longint;
f:array[..,..]of longint;
a:array[..]of longint;
begin
read(n,m);
for i:= to n do
read(a[i]);
f[,]:=;
for i:= to n do
for j:= to m do
begin
if j>=a[i] then f[i,j]:=f[i,j]+f[i-,j-a[i]];
f[i,j]:=f[i,j]+f[i-,j];
end;
writeln(f[n,m]);
end.
小A点菜 二维
var n,m:longint;
i,j:longint;
f:array[..]of longint;
a:array[..]of longint;
begin
read(n,m);
for i:= to n do
read(a[i]);
f[]:=;
for i:= to n do
for j:=m downto a[i] do
if j>=a[i] then f[j]:=f[j]+f[j-a[i]];
writeln(f[m]);
end.
小A点菜 一维
24.洛谷 P1064 金明的预算方案 改良背包 (套路)
这个dp...加了一个附加条件qwq...于是我开始萌比了...悄悄咪咪看了一眼题解瞬间就会了...
因为附件最多只有2个,所以先预处理每一个主件的附件是什么... son[i,1]表示 i 的第一个附件 son[i,2]表示 i 的第二个附件
然后分5类dp主件
①不选这个主件。
②只选这个主件。
③选这个主件外加第一个附件。
④选这个主件外加第二个附件。
⑤选这个主件外加第一个附件和第二个附件。
然后分别dp一下就好了...
方程很长qwq...和普通背包的 f 是一样的含义
f[j]=max(f[j],f[j-a[i]]+a[i]*b[i] ①和②
,f[j-a[i]-son[i,1]]+a[i]*b[i]+a[son[i,1]]*b[son[i,1]] ③
,f[j-a[i]-son[i,2]]+a[i]*b[i]+a[son[i,2]]*b[son[i,2]] ④
,f[j-a[i]-son[i,1]-son[i,2]]+a[i]*b[i]+a[son[i,1]]*b[son[i,1]]+a[son[i,2]]*b[son[i,2]]) ⑤
答案 f[n]
var i,j,k:longint;
cost:int64;
f:array[..]of int64;
son:Array[..,..]of longint;
a,b,c:array[..]of longint;
n,m:longint;
function max(a,b:int64):int64;
begin
if a>b then exit(a) else exit(b);
end;
begin
read(n,m);
for i:= to m do
begin
read(a[i],b[i],c[i]);
if c[i]> then
begin
inc(son[c[i],]);
son[c[i],son[c[i],]]:=i;
end;
end;
for i:= to m do
if c[i]= then
begin
for j:=n downto a[i] do
begin
cost:=a[i]*b[i];
f[j]:=max(f[j],f[j-a[i]]+cost);
for k:= to son[i,] do
if j-a[i]>=a[son[i,k]] then
f[j]:=max(f[j],f[j-a[son[i,k]]-a[i]]+cost+a[son[i,k]]*b[son[i,k]]);
cost:=cost+a[son[i,]]*b[son[i,]]+a[son[i,]]*b[son[i,]];
if j-a[i]>=a[son[i,]]+a[son[i,]] then
f[j]:=max(f[j],f[j-a[i]-a[son[i,]]-a[son[i,]]]+cost);
end;
end;
writeln(f[n]);
end.
P1064 金明的预算方案
懒的筛好题了...只要稍微有些难度的就放进来,不然坑补不完 QAQ...
25.P1049 装箱问题 入门背包dp (入门背包)
noip2001T4这么简单的咩qwq
这题做法很多,我想的是设 f[j] 表示前 i 件物品拼出 j 是否可行,可行则为 true 不可行为 false
答案就是倒着枚举找到一个 f[j]=true 答案为 v-j
如果 f[j-a[i]]=true 则 f[ j ]=f[j-a[i]]
var
f:array[..]of boolean;
i,j:longint;
x,v,n:longint; begin
read(v);
read(n);
f[]:=true;
for i:= to n do
begin
read(x);
for j:=v downto x do
if f[j-x] then f[j]:=true;
end;
for i:=v downto do
if f[i] then
begin
writeln(v-i);
exit;
end;
end.
装箱问题
也可以类似 第23题一样做,如果最后的 f[j]>0 就相当于 f[j]=true 了,但是bool 省空间...所以我打的是bool
还可以将价值看成体积来做一个01背包...
很多做法,做法比较多所以随便也拉进来坑里吧...
26.cf914C 不资道什么dp...可能是数位? (难+套路)
这题的话qwq...想了很久没想法,悄悄咪咪翻了官方题解,没看懂,然后百度,没看懂...只资道是dp...大概看了一些思路,然后就又滚去想了...
我这里将题目中的 n 改为 s。
由于 s 十分大, 有 21000,但是思考一下,发现s只要经过一次操作,就能变得小于1000,因为s在二进制下最多有1000位,假设都为1,则操作后变为1000。
为了方便,可以不直接使用1000,我萌设 n=length(s) 即 s 在二进制下的位数。
而对于 1~1000 中的数,我萌是可以直接暴力判需要多少步变为 1 的。
这里我采用类似dp的一个递推来求。
设 num[i] 表示 i 这个数变为 1 需要的次数。
num[1]=0
num[i]=num[y]+1 (其中,2≤i≤n,y为 i 在二进制下1的个数)
这个用 O(n log n) 可以处理出来。
这时我萌反向思考一下,将问题变为 对于一个数 x,有多少个 y 满足y在二进制下的 1 有 x 个且 y≤n。
而对于原问题,答案即 满足 num[x]=k-1 的 x 能找到的 y 的个数之和。
好的,接下来是真正的dp,解决 找有多少个y的dp。
设 dp[i,j,0] 表示 1~i 中选 j 个 1,且 t[x]=s[x](1≤x≤i,其中 t 表示选出的 y 的二进制,如s=“110”,那dp[3,3,0]=0{能构造出的t=“111”,无法满足与s相同},dp[3,2,0]=1{能构造出的t=“110” t=“101” t=“011”,满足s=t的有1个,为t=“110”})
初值 dp[0,0,0]=1。
设 dp[i,j,1] 表示1~i 中选 j 个1,且存在 x 使 t[x]≠s[x]{具体来说,t[x]=“0” s[x]=“1”} (1≤x≤i,其中 t 表示选出的 y 的二进制,与上面类似,dp[3,3,1]=1,dp[3,2,1]=2)
初值 dp[i,0,1]=1。
转移:
对于 s[i]=“1” 时 :dp[i,j,0]=dp[i-1,j-1,0] {要保证和s[i]取的相同,也就是取1}
dp[i,j,1]=dp[i-1,j,0]{在 i 位置取0使原来的s=t变为s≠t}+dp[i-1,j-1,1]{由于原来就是不同的,说明后面的既可以取1也可以取0,这里为取1的方案}+dp[i-1,j,1]{与上类似,这里是取0的方案}
对于 s[i]=“0” 时 :dp[i,j,0]=dp[i-1,j,0] {要保证和s[i]取的相同,也就是取0}
dp[i,j,1]=dp[i-1,j-1,1]{由于原来就是不同的,说明后面的既可以取1也可以取0,这里为取1的方案}+dp[i-1,j,1]{与上类似,这里是取0的方案}
(注意:不加dp[i-1,j,0]的原因是如果加了就会和s相同,而如果加 dp[i-1,j-1,0]就会大于n,这不是正确的答案。)
对于一个 x 的答案为dp[n,x,1]{y不和s相同且小于s}+dp[n,x,0]{y和s相同} 表示找的 y 的位数是 n,取 x个1构成的 y的个数。
那ans+=dp[n,x,1]+dp[n,x,0] (1≤x≤n,且满足num[x]=k-1)
这样还是会wa的....因为有坑点,当 k=0时,答案为1需特判。
这样还是会wa的....因为还有坑点,当k=1是,答案需减1,因为原答案会构成一个 y=1,而 1 是 0步就可以搞定的,但num[1]=0=k-1 会被统计。
这样还是会wa的....那我也没办法了,因为没有坑点啦~~\(≧▽≦)/~啦啦啦
const HR=;
var s:ansistring;
i,j:longint;
n,k,x,y,ans:longint;
dp:array[..,..,..]of longint;
num:array[..]of longint;
begin
readln(s);
n:=length(s);
read(k);
if k= then
begin
writeln();
exit;
end;
for i:= to n do
begin
x:=i;
y:=;
while x> do
begin
inc(y,x mod );
x:=x>>;
end;
num[i]:=num[y]+;
end;
dp[,,]:=;
for i:= to n do
begin
dp[i,,]:=;
for j:= to i do
begin
if s[i]='' then
begin
dp[i,j,]:=dp[i-,j-,];
dp[i,j,]:=dp[i-,j,]+dp[i-,j-,]+dp[i-,j,];
end else
begin
dp[i,j,]:=dp[i-,j,];
dp[i,j,]:=dp[i-,j,]+dp[i-,j-,];
end;
if dp[i,j,]>=HR then dp[i,j,]:=dp[i,j,] mod HR;
if dp[i,j,]>=HR then dp[i,j,]:=dp[i,j,] mod HR;
end;
end;
for i:= to n do
if num[i]=k- then
begin
inc(ans,(dp[n,i,]+dp[n,i,]));
if ans>=HR then ans:=ans mod HR;
end;
if k= then dec(ans);
writeln(ans);
end.
cf914C
QAQ最近的cf的c题都好难...题解一直翻啊...
27.cf 894C 状压dp (难)
这cf怎么一道比一道难啊qwq...老年菜兔撑不住了QAQ哇的一声哭出来,div2C怎么就状压了啊QAQ
根据以往,可以推出,我看题解了...但是这次官方题解真的是萌比,完全看不懂,大致获取信息,类似状压的dp+素数个数很少。
然后就只能自己想了qwq,反正能练一下状压...
对于1~70中的数质因数分解。
设 dp[i,j] 表示 前 i 个数拼出素数状态为 j 的方案数,状态 j 表示 素数个数,是偶数就是0,是奇数就是1。
初始化 dp[0,0]=1
dp[i+1,j]+=dp[i,j] 不取 a[i]
dp[i+1,j xor c[a[i]]]+=dp[i,j] 取a[i] xor正好能满足,两个 1 或0 则为0 一个0一个1则为1这样的运算。这样才可以相应的更改状态。
c[i] 表示 i 质因数分解后对应的 状态 素数个数是偶数就是0,是奇数就是1。
答案dp[n,0]-1 减去一个空的序列。即都不取的情况。
这样的效率 O(n*219) 因为素数个数有19个。预处理c[i]只要O(70 log2 70 )
因为时间和空间都会挂啊,先优化空间,用滚动数组就好了...
这样当然会TLE(第13个test)辣...因为菜兔选手想不到其他写法了...所以就在搞优化,大致优化为计算出1-i 能拼出的状态 j 而不是全部状态 j。
这样还是TLE(13)...再考虑优化,把相同的数都搞到一起,这样1-i能拼出的状态会少一些,因为相同的数被xor 2次后就边0 了...所以状态不会增加...
这样成功的TLE(14) 了...这时菜兔才意识到优化是不存在的了...考虑打表
考虑认真思考找写法QAQ。假设刚开始所有的数都是不重复的,如果加入一个重复的数,实际上就是让这个重复的数在搞一个相同的dp...
那,是不是有什么规律。于是开始打表找规律了。
假设刚开始所有的数都是不重复的,加入一个重复数字后,答案会在原来基础上 *2+1。
打了多个表后,发现确实是这样的QAQ
于是强行用规律,先dp出不重复的数
效率O(70*219) 然后在 对于多出的重复数字,每多一个就 *2+1
const HR=;
var i,j,k:longint;
v:array[..]of boolean;
can:array[..]of longint;
c:array[..]of int64;
a,have:array[..]of longint;
num,n,x,z,new:longint;
bool:longint;
dp:array[..,..]of longint;
begin
for i:= to do
if not v[i] then
begin
inc(num);
for j:= to div i do
begin
v[j*i]:=true;
x:=j*i;
z:=;
while x mod i= do
begin
x:=x div i;
inc(z);
end;
if (z mod =) then c[j*i]:=c[j*i] xor ( << (num-));
end;
end;
for i:= to do
v[i]:=false;
z:=;
can[z]:=;
v[]:=true;
read(n);
for i:= to n do
begin
read(a[i]);
inc(have[a[i]]);
if not v[c[a[i]]] then
begin
inc(z);
can[z]:=c[a[i]];
v[c[a[i]]]:=true;
end;
end;
dp[,]:=;
bool:=;
for i:= to do
for k:= to have[i] do
begin
new:=z;
for j:= to new do
if dp[bool,can[j]]<> then
begin
inc(dp[-bool,can[j]],dp[bool,can[j]]);
if dp[-bool,can[j]]>=HR then dp[-bool,can[j]]:=dp[-bool,can[j]] mod HR;
inc(dp[-bool,can[j] xor c[i]],dp[bool,can[j]]);
if dp[-bool,can[j] xor c[i]]>=HR then
dp[-bool,can[j] xor c[i]]:=dp[-bool,can[j] xor c[i]] mod HR;
dp[bool,can[j]]:=;
if not v[can[j] xor c[i]] then
begin
inc(z);
can[z]:=can[j] xor c[i];
v[can[j] xor c[i]]:=true;
end;
end;
bool:=-bool;
end;
writeln(dp[bool,]-);
end.
cf894C TLE(14)优化可以学一学
const HR=;
var i,j,k:longint;
v:array[..]of boolean;
can:array[..]of longint;
c:array[..]of int64;
a,have:array[..]of longint;
num,n,x,z,new,ans:longint;
bool:longint;
dp:array[..,..]of longint;
function min(a,b:longint):longint;
begin
if a<b then exit(a) else exit(b);
end;
begin
for i:= to do
if not v[i] then
begin
inc(num);
for j:= to div i do
begin
v[j*i]:=true;
x:=j*i;
z:=;
while x mod i= do
begin
x:=x div i;
inc(z);
end;
if (z mod =) then c[j*i]:=c[j*i] xor ( << (num-));
end;
end;
for i:= to do
v[i]:=false;
z:=;
can[z]:=;
v[]:=true;
read(n);
for i:= to n do
begin
read(a[i]);
inc(have[a[i]]);
if not v[c[a[i]]] then
begin
inc(z);
can[z]:=c[a[i]];
v[c[a[i]]]:=true;
end;
end;
dp[,]:=;
bool:=;
for i:= to do
for k:= to min(have[i],) do
begin
new:=z;
for j:= to new do
if dp[bool,can[j]]<> then
begin
inc(dp[-bool,can[j]],dp[bool,can[j]]);
if dp[-bool,can[j]]>=HR then dp[-bool,can[j]]:=dp[-bool,can[j]] mod HR;
inc(dp[-bool,can[j] xor c[i]],dp[bool,can[j]]);
if dp[-bool,can[j] xor c[i]]>=HR then
dp[-bool,can[j] xor c[i]]:=dp[-bool,can[j] xor c[i]] mod HR;
dp[bool,can[j]]:=;
if not v[can[j] xor c[i]] then
begin
inc(z);
can[z]:=can[j] xor c[i];
v[can[j] xor c[i]]:=true;
end;
end;
bool:=-bool;
end;
ans:=dp[bool,]-;
for i:= to do
for k:= to have[i]- do
begin
ans:=ans*+;
if ans>=HR then ans:=ans mod HR;
end;
writeln(ans);
end.
cf894C AC
没想到自己的代码跑的出奇的快,好吧其实也没多快,但至少也是FPC里最快的辣 ~\(≧▽≦)/~啦啦啦 (其实只是因为FPC选手少QAQ)
28.洛谷P1091 (套路)
这题又是思维题,看了题解QAQ
实际上正着求一次最长上升子序列长度,倒着求一次最长上升子序列长度。
dp[i,0]表示正的 dp[i,1]表示倒着的,具体dp就不讲了,前面有讲到。(好像qwq)
然后枚举 i 使 max=dp[i,1]+dp[i,0]-1 最大就好了 减去一次重复的本身。
然后答案就是 n-max
var n:longint;
i,j:longint;
dp:array[..,..]of longint;
a:array[..]of longint;
mx:longint;
function max(a,b:longint):longint;
begin
if a>b then exit(a) else exit(b);
end;
begin
read(n);
for i:= to n do
begin
read(a[i]);
dp[i,]:=;
dp[i,]:=;
end;
for i:= to n do
for j:= to i do
if a[j]<a[i] then dp[i,]:=max(dp[j,]+,dp[i,]);
for i:=n downto do
for j:=i to n do
if a[j]<a[i] then dp[i,]:=max(dp[j,]+,dp[i,]);
for i:= to n do
if dp[i,]+dp[i,]->mx then mx:=dp[i,]+dp[i,]-;
writeln(n-mx);
end.
luoguP1091
29.cf919D 拓扑+dp (简单dp+拓扑套路)
可以发现路是长的比路是短的要更优,所以实际上路必然是一个入度为0的点跑到出度为0的点。
比赛的时候选择直接spfa跑26次最长路。然后比赛的时候没想到自环是环,然后一直wa4,被zn教做人兔。
wa4的时候随便找出了一些错误,然后把判环改成拓扑而不是spfa。
凌晨1点比赛结束改完交了之后成功 tle(33test)了,然后发现可能被卡spfa了,毕竟spfa玄学效率。
早上中午起床然后打算改成拓扑的时候随便跑个dp就好了,没必要spfa。
然后就过了...(跑的还挺快啊qwq)
设 dp[i,c] 表示 从某个入度为 0 的点出发到达 i 这个点经过的点中能使 c 这个字符的最大值是多少。
dp[i,s[i]]=1 (i为入度为0的点)
接着在拓扑的时候更新一下dp
dp[y,c]=max(dp[y,c],dp[x,c]+cost) (若s[y]=c则cost为1不然为0)
答案就是 max(dp[i,c]) (1≤i≤n,‘a’≤c≤‘z’)
其实不难qwq,比赛的时候想太多了...
type
node=record
y,next:longint;
end;
var i:longint;
n,m,tot:longint;
c:char;
v:array[..]of boolean;
first,goin:array[..]of longint;
dp:array[..,'a'..'z']of longint;
e:array[..]of node;
q:array[..]of longint;
x,y:longint;
ans:longint;
s:ansistring;
procedure adde(x,y:longint);
begin
e[tot].next:=first[x];
e[tot].y:=y;
first[x]:=tot;
inc(tot);
end;
function max(a,b:longint):longint;
begin
if a>b then exit(a) else exit(b);
end;
procedure tupo;
var head,tail:longint;
i,now,y,cost:longint;
c:char;
begin
head:=;
tail:=;
for i:= to n do
if goin[i]= then
begin
inc(tail);
q[tail]:=i;
dp[i,s[i]]:=;
end;
while head<=tail do
begin
now:=q[head];
i:=first[now];
while i<>- do
begin
y:=e[i].y;
for c:='a' to 'z' do
begin
if c=s[y] then cost:= else cost:=;
dp[y,c]:=max(dp[y,c],dp[now,c]+cost);
end;
dec(goin[y]);
if goin[y]= then
begin
inc(tail);
q[tail]:=y;
end;
i:=e[i].next;
end;
inc(head);
end;
if tail<>n then
begin
writeln(-);
halt;
end;
end;
begin
readln(n,m);
readln(s);
for i:= to n do
first[i]:=-;
for i:= to m do
begin
read(x,y);
if x<>y then
begin
adde(x,y);
inc(goin[y]);
end else
begin
writeln(-);
exit;
end;
end;
tupo;
for i:= to n do
for c:='a' to 'z' do
if dp[i,c]>ans then ans:=dp[i,c];
writeln(ans);
end.
cf919D
30.洛谷P1282 简单线性dp (一般,有点套路)
设 dp[i,j] 表示前 i 个多米诺骨牌的差值为 j 时的最小费用。
j 可以为负数 ,c++就移一下...
dp[i,j+a-b]=min(dp[i-1,j])
dp[i,j+b-a]=min(dp[i-1,j]+1)
考虑空间耗损有点多,就滚动数组优化一下,不优化也可以过~\(≧▽≦)/~啦啦啦
var n:longint;
i,j:longint;
a,b:longint;
dp:array[..,-..]of longint;
function min(a,b:longint):longint;
begin
if a<b then exit(a) else exit(b);
end;
begin
read(n);
for i:=- to do
dp[,i]:=maxlongint;
dp[,]:=;
for i:= to n do
begin
read(a,b);
for j:=- to do
dp[i,j]:=maxlongint;
for j:=- to do
if dp[i-,j]<>maxlongint then
begin
dp[i,j+a-b]:=min(dp[i,j+a-b],dp[i-,j]);
dp[i,j+b-a]:=min(dp[i,j+b-a],dp[i-,j]+);
end;
end;
for i:= to do
if min(dp[n,i],dp[n,-i])<>maxlongint then
begin
writeln(min(dp[n,i],dp[n,-i]));
exit;
end;
end.
luoguP1282 无滚动数组
var n:longint;
i,j:longint;
a,b:longint;
dp:array[..,-..]of longint;
bool:longint;
function min(a,b:longint):longint;
begin
if a<b then exit(a) else exit(b);
end;
begin
read(n);
for i:=- to do
begin
dp[,i]:=maxlongint;
dp[,i]:=maxlongint;
end;
dp[,]:=;
bool:=;
for i:= to n do
begin
read(a,b);
for j:=- to do
if dp[bool,j]<>maxlongint then
begin
dp[-bool,j+a-b]:=min(dp[-bool,j+a-b],dp[bool,j]);
dp[-bool,j+b-a]:=min(dp[-bool,j+b-a],dp[bool,j]+);
dp[bool,j]:=maxlongint;
end;
bool:=-bool;
end;
for i:= to do
if min(dp[bool,i],dp[bool,-i])<>maxlongint then
begin
writeln(min(dp[bool,i],dp[bool,-i]));
exit;
end;
end.
luoguP1282 滚动数组
31.洛谷P1020 最长上升序列变形 (数据结构优化dp+套路)
第一问直接求最长不上升序列即可,由于n较大可以用树状数组或其他奇怪的dp+二分写过
由于树状数组似乎很少人写,而且我只会树状数组,所以就写了这个。
因为原来的dp要求 1~i-1 中大于 a[i] 值的dp[j]最大
利用树状数组可以后缀和求最大值。
一般求 l , r的最大值用数状数组实现是要 logn2 的效率。
但是对于这个问题没有必要,因为查询只会查询后缀和的最大值,即只有 l r被固定为最大值了。
所以修改可以直接取max查询也同理取max。
注意:若题目需查询 l~r的最大值及单点修改操作,不可采用这个方法,具体的写法自行百度。个人建议线段树实现。
第二问就是一个套路了,实际上是求最长上升序列。这样保证可以覆盖掉所有数。
就相当于倒着求一个最长下降序列,所以倒着来个树状数组就行了。
注意和第一问不同,这里要下降而不是不上升。
var n,m:longint;
dp,tree,a:array[..]of longint;
i:longint;
function max(a,b:longint):longint;
begin
if a>b then exit(a) else exit(b);
end;
function low(x:longint):longint;
begin
exit(x and -x);
end;
procedure adde(x,d:longint);
begin
while x> do
begin
tree[x]:=max(tree[x],d);
dec(x,low(x));
end;
end;
function getmax(x:longint):longint;
var s:longint;
begin
s:=;
if x= then exit(s);
while x<=m do
begin
s:=max(tree[x],s);
inc(x,low(x));
end;
exit(s);
end;
begin
while not eoln do
begin
inc(n);
read(a[n]);
m:=max(m,a[n]);
end;
for i:= to n do
begin
dp[i]:=getmax(a[i])+;
adde(a[i],dp[i]);
end;
writeln(getmax());
for i:= to m do
begin
tree[i]:=;
dp[i]:=;
end;
for i:=n downto do
begin
dp[i]:=getmax(a[i]+)+;
adde(a[i],dp[i]);
end;
writeln(getmax());
end.
luoguP1020
被某大佬催更了QAQ,初三开学了呀QAQ有时间就补吧...争取暑假转c++之前填完...
32.洛谷P1508 入门dp (入门)
直接金字塔变形...
不细讲了...嗷
var m,n:longint;
i,j:longint;
dp,a:array[..,..]of int64;
ans:int64;
function max(a,b:int64):int64;
begin
if a>b then exit(a) else exit(b);
end;
begin
read(m,n);
for i:= to m do
begin
dp[i,]:=-maxlongint;
dp[i,n+]:=-maxlongint;
for j:= to n do
begin
read(a[i,j]);
dp[i,j]:=-maxlongint;
if i=m then dp[i+,j]:=-maxlongint;
end;
readln;
end;
dp[m+,]:=-maxlongint;
dp[m+,n+]:=-maxlongint;
dp[m+,(n+) div ]:=;
for i:=m downto do
for j:= to n do
begin
dp[i,j]:=max(dp[i,j],dp[i+,j]+a[i,j]);
dp[i,j]:=max(dp[i,j],dp[i+,j-]+a[i,j]);
dp[i,j]:=max(dp[i,j],dp[i+,j+]+a[i,j]);
end;
ans:=-maxlongint;
for i:= to n do
ans:=max(dp[,i],ans);
writeln(ans);
end.
luoguP1508
33.cf 31E 就一个简单的取决dp (入门)
设 dp[i,j] 表示前 i 个字符 有 j 个是A串的 能拿到的最大和
那b串的就有 i-j 个
考虑到和其实可以都拆成每一位上的和
然后就枚举 i 然后dp要这个 s[i] 去A串还是B串就好啦
设num表示s[i]这个字符转成数字是多少
ten[i]表示10的 i 次方(10i)
dp[i+1,j+1]=max(dp[i,j]+num*ten[n-j-1]);
dp[i+1,j]=max(dp[i,j]+num*ten[n-i+j-1]);
这些奇奇怪怪的下标画个图就可以推了
求完dp顺着搞一下每一步的最优都是选哪一个就好了
倒着应该也行,但是我打挂了...懒的调QAQ
var
n:longint;
s:array[..]of char;
ans:array[..,..]of string;
i,j:longint;
ten:array[..]of int64;
num:int64;
dp:array[..,..]of int64;
function max(a,b:int64):int64;
begin
if a>b then exit(a) else exit(b);
end;
function min(a,b:int64):int64;
begin
if a<b then exit(a) else exit(b);
end; begin
readln(n);
ten[]:=;
for i:= to *n do
begin
read(s[i]);
if i<=n then ten[i]:=ten[i-]*;
end;
readln;
for i:= to *n- do
begin
num:=ord(s[i+])-;
for j:=max(,i-n) to min(n,i) do
begin
if (j<n)and(dp[i+,j+]<dp[i,j]+num*ten[n-j-]) then
dp[i+,j+]:=dp[i,j]+num*ten[n-j-]; if (i-j<n)and(dp[i+,j]<dp[i,j]+num*ten[n-i+j-]) then
dp[i+,j]:=dp[i,j]+num*ten[n-i+j-];
end;
end;
for i:= to *n- do
begin
num:=ord(s[i+])-;
for j:=max(,i-n) to min(n,i) do
begin
if (j<n)and(dp[i+,j+]=dp[i,j]+num*ten[n-j-]) then
ans[i+,j+]:=ans[i,j]+'H';
if (i-j<n)and(dp[i+,j]=dp[i,j]+num*ten[n-i+j-]) then
ans[i+,j]:=ans[i,j]+'M';
end;
end;
writeln(ans[*n,n]);
end.
cf31E
34.洛谷P1387 有点不是很常规的dp (较难)
n比较小怎么写都能水
但是dp似乎是最优的
刚开始没想出来只会写个n3的前缀和...看了题解懂了
可能是我比较少写这类型的题
设f[i,j]表示以 i ,j 这个点为右下角能形成的最大正方形的边长
看个图就懂了
所以就是
f[i,j]=min(f[i,j-1],f[i-1,j],f[i-1,j-1])+1
var
n,m:longint;
i,j:longint;
ans:longint;
f,a:array[..,..]of longint;
function min(a,b:longint):longint;
begin
if a<b then exit(a) else exit(b);
end;
begin
read(n,m);
for i:= to n do
begin
for j:= to m do
read(a[i,j]);
readln;
end;
for i:= to n do
begin
for j:= to m do
if a[i,j]= then
begin
f[i,j]:=min(min(f[i-,j],f[i,j-]),f[i-,j-])+;
if f[i,j]>ans then ans:=f[i,j];
end
end;
writeln(ans);
end.
luoguP1387
35.洛谷P1855 二维01背包
裸的
dp[i,j]=max(dp[i-time[k],j-money[k]]+1)
var n,m,t:longint;
i,j,k:longint;
time,mo:array[..]of longint;
dp:array[..,..]of longint;
function max(a,b:longint):longint;
begin
if a>b then exit(a) else exit(b);
end;
begin
read(n,m,t);
for i:= to n do
read(time[i],mo[i]);
for k:= to n do
begin
for i:=m downto mo[k] do
for j:=t downto time[k] do
dp[i,j]:=max(dp[i-mo[k],j-time[k]]+,dp[i,j]);
end;
writeln(dp[m,t]);
end.
luoguP1855
36.洛谷P1541 多维dp
设 dp[i,j,k,p] 表示用了 i 张 1 的 j 张 2 的 k 张 3 的 p 张 4 的
设x=i+j*2+k*3+p*4+1
dp[0,0,0,0]=a[1]
dp[i,j,k,p]=max(dp[i-1,j,k,p],dp[i,j-1,k,p],dp[i,j,k-1,p],dp[i,j,k,p-1])+a[x]
不难...就是多维写起来烦了点,于是数据范围看错了...QAQ
var n,m:longint;
i,j,k,p:longint;
cost:longint;
x:longint;
num:array[..]of integer;
dp:array[-..,-..,-..,-..]of longint;
a:Array[..]of longint;
function max(a,b:longint):longint;
begin
if a>b then exit(a) else exit(b);
end;
begin
read(n,m);
for i:= to n do
read(a[i]);
for i:= to m do
begin
read(x);
inc(num[x]);
end;
dp[,,,]:=a[];
for i:= to num[] do
for j:= to num[] do
for k:= to num[] do
for p:= to num[] do
begin
x:=i+j*+k*+p*+;
cost:=max(dp[i-,j,k,p],dp[i,j-,k,p]);
cost:=max(dp[i,j,k-,p],cost);
cost:=max(dp[i,j,k,p-],cost);
dp[i,j,k,p]:=max(dp[i,j,k,p],cost+a[x]);
end;
writeln(dp[num[],num[],num[],num[]]);
end.
luoguP1541
37.洛谷P1137 拓扑上简单dp (拓扑套路+dp)
设 dp[i] 表示 以 i 为终点能经过的最大值。
dp[y]=max(dp[x]+1)
考虑这个dp不满足无后性,所以跑个拓扑的时候顺便跑dp就好了,可以验证从入度的0的点出发必然比不在入度为0的点出发更优。
type
node=record
y,next:longint;
end;
var n,m:longint;
first,dp,goin,q:Array[..]of longint;
i:longint;
x,y:longint;
e:array[..]of node;
tot:longint; procedure adde(x,y:longint);
begin
e[tot].next:=first[x];
e[tot].y:=y;
first[x]:=tot;
inc(tot);
end;
function max(a,b:longint):longint;
begin
if a>b then exit(a) else exit(b);
end;
procedure tupu;
var head,tail,now:longint;
i:longint;
begin
head:=;
tail:=;
for i:= to n do
if goin[i]= then
begin
inc(tail);
q[tail]:=i;
dp[i]:=;
end;
while head<=tail do
begin
now:=q[head];
i:=first[now];
while i<>- do
begin
y:=e[i].y;
dp[y]:=max(dp[y],dp[now]+);
dec(goin[y]);
if goin[y]= then
begin
inc(tail);
q[tail]:=y;
end;
i:=e[i].next;
end;
inc(head);
end;
end;
begin
read(n,m);
for i:= to n do
begin
first[i]:=-;
dp[i]:=;
end;
for i:= to m do
begin
read(x,y);
inc(goin[y]);
adde(x,y);
end;
tupu;
for i:= to n do
writeln(dp[i]);
end.
luoguP1137
38.洛谷P1272 树形dp(难)
这题突然懵了QAQ 是自己题意理解的不够好...最后的子树并没有要求一定要保留一开始给的数根...
设 dp[x,j] 表示 以x为根的树保留结点数为 j 的子树需要删的最小边数。
dp[x,1]=num[x] num[x]表示x的度数(入度+出度)。
dp[x,j]=min(dp[y,k]+dp[x,j-k]-2,dp[x,j])
一看上去最不好理解应该是"-2" 。 思考对于 已知 以y 为根的答案后,要转移到 以 x为根的 一定要保留 x->y这条边,而 dp[x,j-k] 中包含了删去这条边的代价。
接着是考虑dp[y,k]中是否包含x->y这条边,会发现如果他不包含,那转移后不会是最优的,发现如果包含,dp[y,k]又重复包含了一次删去这条边的代价,但是这条边是要保留的,所以就-2表示减掉两个不应该有的代价。
type
node=record
y,next:longint;
end;
var n,p:longint;
i:longint;
x,y:longint;
v:array[..]of boolean;
dp:array[..,..]of longint;
e:Array[..]of node;
tot,root,ans:longint;
first:array[..]of longint;
function min(a,b:longint):longint;
begin
if a<b then exit(a) else exit(b);
end;
procedure adde(x,y:longint);
begin
e[tot].next:=first[x];
e[tot].y:=y;
first[x]:=tot;
inc(tot);
end;
procedure dfs(x:longint);
var i,j,k:longint;
y:longint;
begin
i:=first[x];
for j:= to p do
dp[x,j]:= << ;
while i<>- do
begin
y:=e[i].y;
dfs(y);
for j:=p downto do
for k:= to j- do
dp[x,j]:=min(dp[x,j-k]+dp[y,k]-,dp[x,j]);
i:=e[i].next;
end;
end;
begin
read(n,p);
for i:= to n do
begin
first[i]:=-;
v[i]:=false;
end;
for i:= to n- do
begin
read(x,y);
adde(x,y);
v[y]:=true;
inc(dp[x,]);
inc(dp[y,]);
end;
for i:= to n do
if not v[i] then root:=i;
dfs(root);
ans:= <<;
for i:= to n do
ans:=min(ans,dp[i,p]);
writeln(ans);
end.
luoguP1272
39.洛谷P1373 思维dp (难)
由于状态用两个人的分数来存的话必然会tle(mle) 所以考虑转换思维(是的我看题解了)
原题中的k我以p表示 ,并 p++。(代码中的p没++)
因为状态实际上只与差有关,所以状态用差来存即可。
dp[i,j,k,0/1] 表示 终点为点 (i,j) 两人的分数之差为 k 0表示这一步由小A走完 1 表示 这一步由uim走完。
dp[i,j,k,0]+=dp[i-1,j,k-a[i,j]+p %p,1]+dp[i,j-1,k-a[i,j]+p %p,1] (两个方向,算一下差值,对于负数可以直接+p处理)
dp[i,j,k,1]+=dp[i-1,j,k+a[i,j] %p,0]+dp[i,j-1,j+a[i,j] %p,0] (同上两个方向)
答案即 ans+=dp[i,j,0,1];
const HR=; var n,m,p:longint;
i,j,k:longint;
cost,ans:longint;
dp:array[..,..,..,..]of longint;
a:array[..,..]of longint;
begin
read(n,m,p);
for i:= to n do
begin
for j:= to m do
begin
read(a[i,j]);
if a[i,j]>=p+ then a[i,j]:=a[i,j] mod (p+);
dp[i,j,a[i,j],]:=;
end;
readln;
end;
for i:= to n do
for j:= to m do
for k:= to p do
begin
cost:=k-a[i,j]+p+;
if cost>=p+ then cost:=cost-(p+);
dp[i,j,k,]:=dp[i,j,k,]+dp[i-,j,cost,]+dp[i,j-,cost,];
if dp[i,j,k,]>=HR then dp[i,j,k,]:=dp[i,j,k,]mod HR;
cost:=k+a[i,j];
if cost>=p+ then cost:=cost-(p+);
dp[i,j,k,]:=dp[i,j,k,]+dp[i-,j,cost,]+dp[i,j-,cost,];
if dp[i,j,k,]>=HR then dp[i,j,k,]:=dp[i,j,k,]mod HR;
end;
for i:= to n do
for j:= to m do
begin
ans:=ans+dp[i,j,,];
if ans>=HR then ans:=ans mod HR;
end;
writeln(ans);
end.
luoguP1373
40.洛谷P1537 拆分背包 (套路,难二进制优化背包)
问题可以转换为是否可以用给定的弹珠能拼出 t/2 的价值。
t+=x*i (1≤i≤6)
由于很多价值都是一样的,可以考虑二进制拆分然后直接01背包就好啦。
var two:array[..]of longint;
f:array[..]of boolean;
t,z,n:longint;
x:longint;
i,j:longint;
a:array[..]of longint;
begin
two[]:=;
for i:= to do
two[i]:=two[i-]*;
while not eof do
begin
inc(z);
n:=;
t:=;
for i:= to do
begin
read(x);
t:=t+x*i;
for j:= to do
if x>=two[j] then
begin
inc(n);
a[n]:=two[j]*i;
x:=x-two[j];
end;
if x> then
begin
inc(n);
a[n]:=x*i;
end;
end;
if t= then exit;
writeln('Collection #',z,':');
if t mod = then
begin
writeln('Can''t be divided.');
writeln;
end else
begin
f[]:=true;
t:=t div ;
for j:= to t do
f[j]:=false;
for i:= to n do
for j:=t downto a[i] do
if f[j-a[i]] then f[j]:=true;
if f[t] then writeln('Can be divided.') else writeln('Can''t be divided.');
writeln;
end;
end;
end.
luoguP1537
41.洛谷P1613 倍增dp (倍增难)
看题解了QAQ菜兔真的菜啊...
设 dp[k,i,j] 表示 i 到 j 是否存在 2k 的距离。(bool数组)
dp[k,i,j]=dp[k-1,i,x] and dp[k-1,x,j]
初始化就是 dp[0,x,y]=1
这个dp并没办法解决问题...但是可以发现这个dp之后,就可以将原图转换成一个可以求最短路的图
若存在 dp[k,i,j]=true 那么 i 和 j 就可以连一条 长度为 1 的边,表示用一次跑路机能到达。
然后folyd跑最短路就ok辣~
有时候看到 2k 要想到倍增!!!
var n,m:longint;
x,y,i,j,k:longint;
dp:array[..,..,..]of boolean;
dis:array[..,..]of longint;
begin
read(n,m);
for i:= to m do
begin
read(x,y);
dp[,x,y]:=true;
end;
for k:= to do
for x:= to n do
for i:= to n do
for j:= to n do
if (dp[k-,i,x])and(dp[k-,x,j]) then dp[k,i,j]:=true;
for i:= to n do
for j:= to n do
if i<>j then
begin
for k:= to do
if (dp[k,i,j]) then dis[i,j]:=;
if dis[i,j]= then dis[i,j]:=maxint;
end;
for k:= to n do
for i:= to n do
for j:= to n do
if dis[i,k]+dis[k,j]<dis[i,j] then dis[i,j]:=dis[i,k]+dis[k,j];
writeln(dis[,n]);
end.
luoguP1613
42.洛谷P1681 类似34(可Ctrl+F 查找“34.”)
基本上一样
设 f[i,j,0/1] 表示 以 (i,j) 权值为0/1 为右下角的最大边长
则可以延伸的最大边长就是
themax=min(f[i-1,j,1/0],f[i,j-1,1/0],f[i-1,j-1,0/1]) (注意1 0 的互换)
f[i,j,a[i,j]]=themax+1;
var n,m:longint;
i,j:longint;
ans,themax:longint;
f:Array[..,..,..]of longint;
a:Array[..,..]of longint;
function min(a,b:longint):longint;
begin
if a<b then exit(a) else exit(b);
end;
begin
read(n,m);
for i:= to n do
begin
for j:= to m do
begin
read(a[i,j]);
f[i,j,a[i,j]]:=;
end;
readln;
end;
for i:= to n do
for j:= to m do
begin
themax:=min(f[i-,j,-a[i,j]],f[i,j-,-a[i,j]]);
themax:=min(themax,f[i-,j-,a[i,j]]);
f[i,j,a[i,j]]:=themax+;
end;
for i:= to n do
for j:= to m do
if f[i,j,a[i,j]]>ans then ans:=f[i,j,a[i,j]];
writeln(ans);
end.
luoguP1681
43.洛谷P3800 单调队列优化dp (数据结构优化dp)
dp[i,j] 表示第 i 层 位于第 j 列的最优解
dp[i,j]=max(dp[i-1,k]+a[i,j]) (j-t≤k≤j+t)
然后发现这个dp可以单调队列优化,还可以滚动数组优化...
但是我滚动就懒得写了
var n,m,k,t,c,max:longint;
i,j,x,y:longint;
head,tail:longint;
dp,a:array[..,..]of longint;
q:array[..]of longint;
begin
read(n,m,k,t);
for i:= to k do
begin
read(x,y,c);
a[x,y]:=c;
end;
for i:= to n do
begin
for j:= to m do
q[j]:=;
head:=;
tail:=;
for x:= to t do
begin
while (dp[i-,x]>=dp[i-,q[tail]])and(tail>) do dec(tail);
inc(tail);
q[tail]:=x;
end;
for j:= to m do
begin
while (q[head]<j-t)and(head<=tail) do inc(head);
if t+j<=m then
begin
while (dp[i-,t+j]>=dp[i-,q[tail]])and(tail>=head) do dec(tail);
inc(tail);
q[tail]:=t+j;
end;
if head>tail then dp[i,j]:=a[i,j] else
dp[i,j]:=dp[i-,q[head]]+a[i,j];
end;
end;
for i:= to m do
if dp[n,i]>max then max:=dp[n,i];
writeln(max);
end.
luoguP3800
43题辣!~
44.洛谷P1417 排序dp (贪心+dp)
很显然一个简单的01背包
但真的这么容易?还要考虑吧选物品的顺序
考虑相邻两个物品 i , j 的价值和的选择有
i 物品先选 a[i]-t*b[i]+a[j]-(t+c[i])*b[j] ①
j 物品先选 a[j]-t*b[j]+a[i]-(t+c[j])*b[i] ②
那考虑先选 i 的价值更大时
①>② 化简得 b[j]*c[i]<b[i]*c[j]
所以 i 先选时 i 与 j 的先后顺序就可以确定 排序一下即可
var t,n,ans:int64;
i,j:longint;
dp,a,b,c:array[..]of int64;
function max(a,b:int64):int64;
begin
if a>b then exit(a) else exit(b);
end;
procedure qs(l,r:longint);
var i,j:longint;
t,x,y:int64;
begin
i:=l;
j:=r;
x:=b[(l+r)>>];
y:=c[(l+r)>>];
repeat
while c[i]*x<b[i]*y do inc(i);
while c[j]*x>b[j]*y do dec(j);
if i<=j then
begin
t:=a[i];a[i]:=a[j];a[j]:=t;
t:=b[i];b[i]:=b[j];b[j]:=t;
t:=c[i];c[i]:=c[j];c[j]:=t;
inc(i);
dec(j);
end;
until i>j;
if l<j then qs(l,j);
if i<r then qs(i,r);
end;
begin
read(t,n);
for i:= to n do
read(a[i]);
for i:= to n do
read(b[i]);
for i:= to n do
read(c[i]);
qs(,n);
for i:= to n do
for j:=t downto c[i] do
dp[j]:=max(dp[j-c[i]]+a[i]-j*b[i],dp[j]);
for i:= to t do
ans:=max(ans,dp[i]);
writeln(ans);
end.
luoguP1417
45.洛谷P1057阶段dp
f[i,j] 表示传第 i 次 球在 j 手中的方案
f[i,j]=f[i-1,L]+f[i-1,R] L为 j 的左边 R为 j 的右边
初始化 f[0,1]=1 没传之前球在 1号 手里
答案f[m,1]
var n,m,l,r:longint;
i,j:longint;
f:array[..,..]of longint;
begin
read(n,m);
f[,]:=;
for i:= to m do
for j:= to n do
begin
l:=j-;
if l= then l:=n;
r:=j+;
if r=n+ then r:=;
f[i,j]:=f[i-,l]+f[i-,r];
end;
writeln(f[m,]);
end.
luoguP1057
46.洛谷P1122 树形dp
dp[i] 表示 以 i 为根的子树的最大和
初始值dp[i]=cost[i]
dp[i]+=max(dp[x],0)
ans=max(dp[i])
注意一下y=fa的时候要跳过转移,因为不可以拿父亲节点更新儿子节点。
type
node=record
y,next:longint;
end;
var
tot,n,ans:longint;
i:longint;
x,y:longint;
dp,cost,first:array[..]of longint;
e:Array[..]of node;
function max(a,b:longint):longint;
begin
if a>b then exit(a) else exit(b);
end;
procedure adde(x,y:longint);
begin
e[tot].next:=first[x];
e[tot].y:=y;
first[x]:=tot;
inc(tot);
end;
procedure dfs(x,fa:longint);
var i,y:longint;
begin
i:=first[x];
while i<>- do
begin
y:=e[i].y;
if y<>fa then dfs(y,x) else
begin
i:=e[i].next;
continue;
end;
dp[x]:=dp[x]+max(dp[y],);
i:=e[i].next;
end;
end;
begin
read(n);
for i:= to n do
begin
read(cost[i]);
dp[i]:=cost[i];
first[i]:=-;
end;
for i:= to n- do
begin
read(x,y);
adde(x,y);
adde(y,x);
end;
dfs(,);
for i:= to n do
ans:=max(ans,dp[i]);
writeln(ans);
end.
luoguP1122
47.51nod 1242 矩阵快速幂模板题
斐波那契用矩阵快速幂优化即可
type
arr=array[..,..]of int64;
const
HR=;
t:arr=((,),
(,));
a:arr=((,),
(,));
var n:int64;
y,f:arr;
procedure tonew(n,m:int64;var a:arr;b:arr);
var
i,j,k:longint;
begin
for i:= to do
for j:= to m do
begin
for k:= to do
begin
f[i,j]:=f[i,j]+(a[i,k]*b[k,j] mod n);
if f[i,j]>n then f[i,j]:=f[i,j] mod n;
end;
end;
a:=f;
for i:= to do
for j:= to do
f[i,j]:=;
end;
function power(b,n:int64):int64;
begin
y:=a;
while b> do
begin
if b and = then tonew(n,,t,y);
tonew(n,,y,y);
b:=b >> ;
end;
f[,]:=;
f[,]:=;
tonew(n,,t,f);
exit(t[,]);
end;
begin
read(n);
writeln(power(n,HR));
end.
51nod1242
48.洛谷P1108 dp上dp
QAQ看题解了...菜兔啊
第一问很简单...dp[i] 表示最长下降子序列长度
第二问: f[i] 表示 以 i 结尾的长度为 dp[i] 的方案数
f[i]+=f[j] (若 j 能转移到 i 即dp[i]=dp[j]+1 且 a[j]>a[i])
但是这样没有考虑重复状态,这里的重复是按子序列的数字而不是子序列原下标,所以不好处理...
考虑 dp[i]=dp[j] 且 a[i]=a[j] 则说明 i 与 j 实际上是同一个方案,那么 i 能转移的k 就只需要加上一次 f[i] 或者加上一次 f[j] 就好了
那就强行把 f[j] 置0
var sum,ans:int64;
dp,f,a:Array[..]of int64;
i,j:longint;
n:longint;
function max(a,b:int64):int64;
begin
if a>b then exit(a) else exit(b);
end;
begin
read(n);
for i:= to n do
read(a[i]);
a[]:=maxlongint;
for i:= to n do
begin
for j:= to i- do
if a[j]>a[i] then dp[i]:=max(dp[i],dp[j]+);
end;
for i:= to n do
ans:=max(ans,dp[i]);
f[]:=;
for i:= to n do
for j:= to i- do
begin
if (dp[i]=dp[j]+)and(a[j]>a[i]) then f[i]:=f[i]+f[j];
if (dp[i]=dp[j])and(a[i]=a[j]) then f[j]:=;
end;
for i:= to n do
if dp[i]=ans then inc(sum,f[i]);
writeln(ans,' ',sum);
end.
luoguP1108
49.洛谷P1504 01背包
题目可以转换为在所以城堡中分别选出任意个积木都能拼出的最大高度
对于每一个城堡都做一个01背包,dp[i] 表示 是否可以拼出 高度为 i 。
然后用个can[i] 记录有几个 城堡可以拼出高度为 i
ans为 can[i]=n 的最大 i
var n:longint;
x:longint;
i,j:longint;
can:Array[..]of longint;
dp:Array[..]of boolean;
begin
read(n);
for i:= to n do
begin
x:=;
dp[]:=true;
while x>= do
begin
read(x);
if x>= then
begin
for j:= downto x do
if dp[j-x] then dp[j]:=true;
end;
end;
for j:= to do
if dp[j] then
begin
dp[j]:=false;
inc(can[j]);
end;
end;
can[]:=n;
for j:= downto do
if can[j]=n then
begin
writeln(j);
exit;
end;
end.
luoguP1504
50.洛谷P1754 简单dp
最后一题辣,简单点咯..
dp[i,j] 表示 前 i 个人 有 j 个是50元的方案
dp[i,j]+=dp[i-1,j-1] j>0
dp[i,j]+=dp[i-1,j] i-j>=j
然后ans+=dp[2*n,i]
QAQ 统计答案忘记敲个 2* ,于是wa了惨兮兮..
var n:longint;
i,j:longint;
ans:int64;
dp:array[..,..]of int64;
function min(a,b:longint):longint;
begin
if a<b then exit(a) else exit(b);
end;
begin
read(n);
dp[,]:=;
for i:= to *n do
for j:= to min(i,n) do
begin
if j> then dp[i,j]:=dp[i-,j-];
if i-j<=j then inc(dp[i,j],dp[i-,j]);
end;
for i:= to n do
inc(ans,dp[*n,i]);
writeln(ans);
end.
luoguP1754
未完待续
~完结撒花~
自己挖的坑,跪着也要填完!!!
自己挖的坑,终于跪着补完了
动态规划dp专题练习的更多相关文章
- 动态规划DP的优化
写一写要讲什么免得忘记了.DP的优化. 大概围绕着"是什么","有什么用","怎么用"三个方面讲. 主要是<算法竞赛入门经典>里 ...
- 动态规划dp
一.概念:动态规划dp:是一种分阶段求解决策问题的数学思想. 总结起来就一句话:大事化小,小事化了 二.例子 1.走台阶问题 F(10):10级台阶的走法数量 所以:F(10)=F(9)+F(8) F ...
- 算法-动态规划DP小记
算法-动态规划DP小记 动态规划算法是一种比较灵活的算法,针对具体的问题要具体分析,其宗旨就是要找出要解决问题的状态,然后逆向转化为求解子问题,最终回到已知的初始态,然后再顺序累计各个子问题的解从而得 ...
- 决策单调性优化dp 专题练习
决策单调性优化dp 专题练习 优化方法总结 一.斜率优化 对于形如 \(dp[i]=dp[j]+(i-j)*(i-j)\)类型的转移方程,维护一个上凸包或者下凸包,找到切点快速求解 技法: 1.单调队 ...
- 状压dp专题复习
状压dp专题复习 (有些题过于水,我直接跳了) 技巧总结 : 1.矩阵状压上一行的选择情况 \(n * 2^n\) D [BZOJ2734][HNOI2012]集合选数 蒻得不行的我觉得这是一道比较难 ...
- 树形dp专题总结
树形dp专题总结 大力dp的练习与晋升 原题均可以在网址上找到 技巧总结 1.换根大法 2.状态定义应只考虑考虑影响的关系 3.数据结构与dp的合理结合(T11) 4.抽直径解决求最长链的许多类问题( ...
- 区间dp专题练习
区间dp专题练习 题意 1.Equal Sum Partitions ? 这嘛东西,\(n^2\)自己写去 \[\ \] \[\ \] 2.You Are the One 感觉自己智力被吊打 \(dp ...
- 「动态规划」-数位dp专题
数位dp,今天学长讲的稍玄学,课下花了一会时间仔细看了一下,发现板子是挺好理解的,就在这里写一些: 数位dp主要就是搞一些在区间中,区间内的数满足题目中的条件的数的个数的一类题,题目一般都好理解,这时 ...
- 【dp专题】NOIP真题-DP专题练习
这里学习一下DP的正确姿势. 也为了ZJOI2019去水一下做一些准备 题解就随便写写啦. 后续还是会有专题练习和综合练习的. P1005 矩阵取数游戏 给出$n \times m$矩阵每次在每一行取 ...
随机推荐
- Spark Streaming源码分析 – DStream
A Discretized Stream (DStream), the basic abstraction in Spark Streaming, is a continuous sequence o ...
- Macbook pro 13" Installs Ubuntu 18.04
最新版的 MacBook对Ubuntu的支持(或者反过来?)不是很好,但是除了Suspend和resume功能没找到方法使用外,其他都还好,可用. 1.mac在OSX中先安装refind引导,如果开启 ...
- JPA 入门程序及相关注解
1. 概述 JPA(Java Persistence API):用于对象持久化的API; JPA本质上是一种ORM规范,不是ORM框架;提供了一些编程的API接口; Hibernate是实现; 1.1 ...
- Unmet dependencies. Try 'apt-get -f install' with no packages
在ubuntu14.04上用sudo apt-get install percona-xtrabackup安装xtrabackup时提示 zhj@my-SERVER:~$ sudo apt-get i ...
- python学习笔记——字符串
类方法string.upper(str)需要引入string模块,实例方法str.upper()不需要引入string模块 无与伦比的列表解析功能 # coding=utf-8 # 列表解析 prin ...
- Redis在实际项目中的一应用场景
1.在游戏的等级排名,可以将用户信息放入到redis的有序集合中,然后取得相应的排名,不用自己写代码去排序. 2.利用rediss的数据特性的自增,自减属性,可以将项目中的一些列入阅读数,点赞数放入到 ...
- git学习------>在CenterOS系统上安装GitLab并自定义域名访问GitLab管理页面
目前就职的公司一直使用SVN作为版本管理,现在打算尝试从SVN迁移到Git.安排我来预言并搭建好相关的环境以及自己尝试使用Git.今天我就尝试在Center OS系统上安装GitLab,现在在此记录一 ...
- java-mybaits-00503-延迟加载
1.什么是延迟加载 resultMap可以实现高级映射(使用association.collection实现一对一及一对多映射),association.collection具备延迟加载功能. 需求: ...
- mysql 下的命令
1.查看mysql日志vim /var/log/mysqld.log
- PKU 1208 The Blocks Problem(模拟+list应用)
题目大意:原题链接 关键是正确理解题目意思 首先:介绍一下list容器的一些操作:参考链接 list<int> c1; c1.unique(); 去重. c1.r ...