【做题笔记】动态规划专题(DP)

1|0动态规划专题(DP)

这里记录笔者这几天做的有关于 dp 的题目。

1|1树形 dp

1|0- 洛谷 P1122

题目链接:https://www.luogu.com.cn/problem/P1122

题意:选出一个联通分量,使得联通分量的点的点权和最大。

思路:考虑以 fi 表示以点 i 为根节点子树联通分量权值的最大值。

于是初始化有 fi=ai,因为他的子树不管断掉几个一定包含自己。

如果不是叶子节点,考虑如果它儿子(设为 j)的 fj>0,则这个节点一定对 fi 有贡献,加上它的子树会使得答案值更大,否则不选。

于是就有了递推方程:

fi=json(i)fjIf fj>0

#include<bits/stdc++.h> using namespace std; const int N = 2e4 + 5; inline int read() { int x(0),f(0); char ch=getchar(); for(; !isdigit(ch); ch=getchar()) f|=(ch=='-'); for(; isdigit(ch); ch=getchar()) x=(x<<1)+(x<<3)+(ch^48); return f?-x:x; } int val[N],dp[N]; int n,m,ans=numeric_limits<int>::min(); struct graph { int to,nxt; } G[N<<1]; int cnt,head[N]; void addEdge(int u,int v) { G[++cnt]= {v,head[u]}; head[u]=cnt; } void dfs(int x,int fa) { dp[x]=val[x]; for(int i=head[x]; i; i=G[i].nxt) { int t=G[i].to; if(t==fa) continue; dfs(t,x); if(dp[t]>0) dp[x]+=dp[t]; } } int main() { n=read(); for(int i=1; i<=n; i++) val[i]=read(); for(int i=1; i<n; i++) { int u,v; u=read(), v=read(); addEdge(u,v); addEdge(v,u); } dfs(1,-1); for(int i=1; i<=n; i++) ans=max(ans,dp[i]); printf("%d\n",ans); }

1|2线性 dp

1|0- 洛谷 P8725

题目链接:https://www.luogu.com.cn/problem/P8725

fi,j 表示第 i 秒时用了 j 点体力的合法方案。

显然当前位置即为 i2×j。如果 i2×jd 则显然不合法。

如果合法,考虑当前位置可能从哪几个位置转移而来:

  1. 上一秒体力为 j1,这一秒花了体力,即 fi1,j1
  2. 上一秒体力为 j,这一秒没花,即 fi1,j

值得一提的是,如果 j=0,则第一种情况不合法,取第二种情况即可。

于是状转方程就有了:

fi,j={0If i2×jdfi1,jIf j=0fi1,j1+fi1,jOtherwise

注意取模。

#include<bits/stdc++.h> using namespace std; const int N = 3e3 + 5; const int mod = 1e9 + 7; inline int read() { int x(0),f(0); char ch=getchar(); for(; !isdigit(ch); ch=getchar()) f|=(ch=='-'); for(; isdigit(ch); ch=getchar()) x=(x<<1)+(x<<3)+(ch^48); return f?-x:x; } int d,t,m; int dp[N][N]; int main() { d=read(), t=read(), m=read(); dp[0][0]=1; for(int i=1; i<=t; i++) { for(int j=0; j<=min(i,m); j++) { int now = i - j * 2; if(now >= d) continue; if(j == 0) dp[i][j] = dp[i-1][j] % mod; else dp[i][j] = (dp[i][j] + dp[i-1][j] + dp[i-1][j-1]) % mod; } } printf("%d\n",dp[t][m]); }

1|0- 洛谷 P8656

我们注意到,如果 aimodkajmodk,那么这两种实力的人是互不影响的,也就是说,和 ai 有关的选择和和 aj 有关的选择毫无关系,我们考虑把 {a} 按照 modk 的值分成 k 类。

在每一类中,我们先排序,再去重成不重复的元素,然后就是经典的线性 dp 了:设 fi,0/1,表示第 i 个元素结尾最多能取 fi,0/1 个人。取第 i 个即为 fi,1,否则为 fi,0

于是状转方程就很容易推了,设 diai 的出现次数,paimodk 的值:

fi,1={diIf i=0fi1,0+diIf (ap,iap,i1)=kmax(fi1,0,fi1,1)+diOtherwise.

fi,0={0If i=0max(fi1,1,fi1,0)Otherwise

注意当 (ap,iap,i1)=k 时只能选一种。以及 dp 数组记得每一次之前都要初始化。

记得当 k=0 的时候要特判,其最优解即为每个元素都选一个,不判的话在 C++ 中 mod0 会 RE。

#include<bits/stdc++.h> using namespace std; const int N = 2e5 + 5; const int mod = 1e9 + 7; inline int read() { int x(0),f(0); char ch=getchar(); for(; !isdigit(ch); ch=getchar()) f|=(ch=='-'); for(; isdigit(ch); ch=getchar()) x=(x<<1)+(x<<3)+(ch^48); return f?-x:x; } int n,k,ans; int a[N],d[N],g[N]; int dp[N][2]; vector<int> v[N]; int main() { n=read(), k=read(); for(int i = 1; i <= n; i++) a[i]=read(),d[a[i]]++;; sort(a+1, a+n+1); int m = unique(a+1, a+n+1)-a-1; if(k == 0) { printf("%d\n", m); return 0; } for(int i = 1; i <= m; i++) { g[a[i]]++; if(g[a[i]] == 1) v[a[i] % k].push_back(i); } // dp for(int i = 0; i < k; i++) { if(v[i].empty()) continue; int siz = v[i].size(); memset(dp, 0x3f, (siz + 5) * sizeof(dp[0][0])); for(int j = 0; j < siz; j++) { if(j == 0) dp[0][1] = d[a[v[i][j]]], dp[0][0] = 0; else if((a[v[i][j]] - a[v[i][j-1]]) == k) { dp[j][0] = max(dp[j-1][1], dp[j-1][0]); dp[j][1] = dp[j-1][0] + d[a[v[i][j]]]; } else { dp[j][0] = max(dp[j-1][1], dp[j-1][0]); dp[j][1] = max(dp[j-1][1], dp[j-1][0]) + d[a[v[i][j]]]; } } ans += max({dp[siz - 1][0], dp[siz - 1][1]}); } printf("%d\n",ans); }

1|0- 洛谷 P8786

闲话:109+7= 0x3b9aca07

状态设计:设 fi,j,k 为第 i 秒,看了 j 次花,酒量为 k 时,合法的可能数。

首先我们要明确一个范围:酒显内酒的最大值是多少?显然不会超过 100,不然即使全是花也不能喝空。

我们不从它前面的转移到它,从它转移到它后面的:

如果 fi,j,k0,那么当前有合法的可能数,于是:

  1. 如果 k>0,即酒显没空,则它可以转移到 fi+1,j+1,k1,即这一秒看了花。
  2. 如果 k<50,即酒显没空,且加酒后不超过 100,则它可以转移到 fi+1,j,k×2,即这一秒加了酒。

注意 j0m1,因为最后一次要留给花。

#include<bits/stdc++.h> using namespace std; using ll = long long; const int N = 1e2 + 5; const int mod = 0x3b9aca07; inline int read() { int x(0),f(0); char ch=getchar(); for(; !isdigit(ch); ch=getchar()) f|=(ch=='-'); for(; isdigit(ch); ch=getchar()) x=(x<<1)+(x<<3)+(ch^48); return f?-x:x; } int n,m; int ans,dp[N + N][N][N]; // time i, flower met j, wine left k inline void getmod(int &x){ x %= mod; } int main() { n=read(), m=read(); dp[0][0][2] = 1; for(int i = 0; i <= n + m; i++) { for(int j = 0; j < m; j++) { for(int k = 1; k <= m; k++) { if(dp[i][j][k]){ if(k > 0) getmod(dp[i + 1][j + 1][k - 1] += dp[i][j][k]); if(k <= 50) getmod(dp[i + 1][j][k * 2] += dp[i][j][k]); } } } } printf("%d\n", dp[n + m][m][0]); }

1|3状压 dp

1|0- 洛谷 P8687

闲话:最优解 rk1 是用 IDA* 做的,每个点都是 4ms,恐怖如斯。

对于 1n100,1m20,想到状压 dp。

设计状态:用一个整数的二进制表示,第 i 位为 1 则有这种类型的糖果,反之没有。

Θ(n×2m) 的时间枚举每一个店铺,把能更新的就更新掉,即

fai | state=min(fai | state,fai+1)

其中 state 表示已经有的状态,这个可以从 12m 枚举。

时间复杂度是 Θ(n×2m) 的,可以卡过去。

#include<bits/stdc++.h> using namespace std; const int N = 20; inline int read() { int x(0), f(0); char ch = getchar(); for(; !isdigit(ch); ch=getchar()) f |= (ch == '-'); for(; isdigit(ch); ch=getchar()) x = (x << 1) + (x << 3) + (ch ^ 48); return f ? -x : x; } int n,m,k,z; int dp[1<<N],a[N]; int main() { memset(dp, 0x3f, sizeof dp); n=read(), m=read(), k=read(); for(int i = 1; i <= n; i++) { int x = 0, y; for(int i = 1; i <= k; i++) { y = read() - 1; x |= (1 << y); } dp[x] = 1; a[i] = x; z |= x; } if(z + 1 != (1 << m)) return puts("-1") & 0; int ed = (1 << m) - 1; for(int i = 1; i <= n; i++){ for(int state = 0; state <= ed; state++){ if(dp[state] > 200) continue; int to = a[i] | state; dp[to] = min(dp[to], dp[state] + 1); } } printf("%d\n", dp[ed]); return 0; }

1|0- 洛谷 P1433

闲话:关于搜索卡时,他死了。

待填坑。


__EOF__

本文作者TheSky233
本文链接https://www.cnblogs.com/TheSky233/p/17043410.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   TheSky233  阅读(102)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示