DP训练笔记
预计时间一个月,一天的量为1-2道左右,难度不均,黄-紫都有,后阶段紫
// https://www.luogu.com.cn/problem/P4141 // 对于任何一个物品对答案的贡献都是从1到n递推过去的,所以 // 只需要开一个相同的数组然后从头遍历一遍,把该物品对答案的贡献减去就可以了 #include<bits/stdc++.h> using namespace std; const int N=4020; int dp[N],res[N],w[N],n,m; int main() { cin>>n>>m; for(int i=1;i<=n;i++) cin>>w[i]; dp[0]=1,res[0]=1; for(int i=1;i<=n;i++) for(int j=m;j>=w[i];j--) dp[j]=(dp[j]+dp[j-w[i]])%10; for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(j<w[i]) res[j]=dp[j]; //对于每个包含于i的物品答案数,用dp数-去未包含他的数即为答案数 else res[j]=(dp[j]-res[j-w[i]]+10)%10;//别忘了相减取模要+模数 cout<<res[j]; } cout<<endl; } return 0; }
//https://www.luogu.com.cn/problem/CF607B #include <bits/stdc++.h> #define int long long using namespace std; const int N=2040,mod=1e9+7; int n,t,dp[N][N],a[N]; signed main() { std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0); cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=n;i>=1;i--){ //区间dp,这样的顺序是对的 for(int j=i;j<=n;j++){ //初始化: if(i==j) {dp[i][j]=1;continue;} if(i+1==j) {dp[i][j]=(a[i]==a[j]?1:2);continue;} dp[i][j]=1e18; //枚举区间长度 for(int k=i;k<j;k++) dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]); if(a[i]==a[j]) dp[i][j]=min(dp[i][j],dp[i+1][j-1]); } } cout<<dp[1][n]; return 0; }
//https://www.luogu.com.cn/problem/CF1513C //可以肯定的是,对于任何一位的数来说,他能变得长度是确定得 //例如 9,它经过2两次变化长度一定是2,其它位对9这一位没影响 //所以先预处理来每个数能够变成得最多长度 #include <bits/stdc++.h> #define int long long using namespace std; const int N=3e5+10,mod=1e9+7; string s; int n,t,a[N],f[N],res,num,ans,m,k; vector<vector<int>>dp(N+1,vector<int>(10)); void solve() { cin>>s>>n; res=0; for(char c:s) res=(res+dp[n][c-'0'])%mod; cout<<res<<endl; } signed main() { std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0); cin>>t; //dp[i][j]为,数字j经过i次变换之后有多长 //状态转移为 : 9先由1位变成2位,然后下一次变化后,8就是9,7就是8,以此类推 for(int i=0;i<=9;i++) dp[0][i]=1; for(int i=1;i<=N;i++){ for(int j=0;j<=8;j++) dp[i][j]=dp[i-1][j+1]%mod; dp[i][9]=(dp[i-1][1]+dp[i-1][0])%mod; // for(int j=0;j<=9;j++) cout<<dp[i][j]<<' ';cout<<endl; } while(t--){ solve(); } return 0; } // //另一种思路: 逢10进1,就有: // if(i+j>=10) dp[i][j]=dp[i+j-10][0]+dp[i+j-10][1]%mod; // else dp[i][j]=1; // 如果i+j>10,那么操作后是两位,分别是1和0,而从j到10需要10-j步,还剩i+j-10步 #include <bits/stdc++.h> #define int long long using namespace std; const int N=3e5+10,mod=1e9+7; string s; int n,t,a[N],f[N],res,num,ans,m,k; vector<vector<int>>dp(N+1,vector<int>(10)); void solve() { cin>>s>>n; res=0; for(char c:s) res=(res+dp[n][c-'0'])%mod; cout<<res<<endl; } signed main() { std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0); cin>>t; //dp[i][j]为,数字j经过i次变换之后有多长 //状态转移为 : 9先由1位变成2位,然后下一次变化后,8就是9,7就是8,以此类推 for(int i=0;i<=9;i++) dp[0][i]=1; for(int i=1;i<=N;i++){ for(int j=0;j<=9;j++) if(i+j>=10) dp[i][j]=dp[i+j-10][0]+dp[i+j-10][1]%mod; else dp[i][j]=1; // for(int j=0;j<=9;j++) cout<<dp[i][j]<<' ';cout<<endl; } while(t--){ solve(); } return 0; }
//https://www.luogu.com.cn/problem/CF711C #include <bits/stdc++.h> #define int long long using namespace std; const int N=205; int dp[N][N][N],res=1e18,n,m,x; /* dp[i][j][k] 是第i个树,第j个颜色,当前是第k组 col[i]是当前的染色情况 c是 n x m 的花费 */ int col[N],c[N][N]; signed main() { std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0); cin>>n>>m>>x; for(int i=1;i<=n;i++) cin>>col[i]; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) cin>>c[i][j]; //初始化 memset(dp,0x3f,sizeof dp); //如果说第一位已经被染了,那么就不管了,因为要从第一位开始递推,如果没染那就都染一遍 if(col[1]) dp[1][col[1]][1]=0; else for(int i=1;i<=m;i++) dp[1][i][1]=c[1][i]; for(int i=2;i<=n;i++){ if(col[i]){ //如果已经确定被染色 for(int j=1;j<=m;j++){ for(int k=1;k<=x;k++){ if(j!=col[i]) dp[i][col[i]][k]=min(dp[i][col[i]][k],dp[i-1][j][k-1]); //不同组 else dp[i][col[i]][k]=min(dp[i][col[i]][k],dp[i-1][col[i]][k]);// 同组 } } } else{ for(int j=1;j<=m;j++){ for(int h=1;h<=m;h++){ for(int k=1;k<=x;k++){ if(j!=h) dp[i][j][k]=min(dp[i][j][k],dp[i-1][h][k-1]+c[i][j]); else dp[i][j][k]=min(dp[i][j][k],dp[i-1][j][k]+c[i][j]); } } } } } if(col[n]) res=dp[n][col[n]][x]; else for(int i=1;i<=m;i++) res=min(res,dp[n][i][x]); cout<<(res>=1e18/2?-1:res); return 0; }
//https://codeforces.com/contest/706/problem/C //考虑dp,类似于染色树,把情况考虑全即可 #include<bits/stdc++.h> #define int long long using namespace std; const int N=1e5+10,inf=0x7fffffffffff; string s[N],rev_s[N]; int n,res,dp[N][2],c[N]; signed main() { std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0); cin>>n; for(int i=1;i<=n;i++) cin>>c[i]; for(int i=1;i<=n;i++){ dp[i][1]=dp[i][0]=inf; cin>>s[i];rev_s[i]=s[i]; reverse(rev_s[i].begin(),rev_s[i].end()); } dp[1][0]=0,dp[1][1]=c[1]; for(int i=2;i<=n;i++){ if(s[i]>=s[i-1]) dp[i][0]=dp[i-1][0]; if(rev_s[i]>=rev_s[i-1]) dp[i][1]=dp[i-1][1]+c[i]; if(rev_s[i]>=s[i-1]) dp[i][1]=min(dp[i][1],dp[i-1][0]+c[i]); if(s[i]>=rev_s[i-1]) dp[i][0]=min(dp[i][0],dp[i-1][1]); } if(min(dp[n][0],dp[n][1])==inf) cout<<"-1"; else cout<<min(dp[n][0],dp[n][1]); }
//https://www.luogu.com.cn/problem/P2758 #include<bits/stdc++.h> using namespace std; const int N=2050; string st=" ",ed=" ",s,ss; int dp[N][N],res,lst,n,m; //第一维存A串,二维存B串,意思是从A到B需要用的次数 /* 1.确定子问题: 由于对于字符串的操作只有4种情况(删除,添加、更改、不变) 所以该题的子问题就是进行了这4种操作后的A字符串变为B字符串需要多少步。 2.定义状态: 也就是说递归的dp函数需要哪些参数,参数越少越好因为需要建memo。 后来想到dp(i,j)代表字符串A的前i个字符(包括第i个)变为字符串B的前j个(包括第j个)需要多少步。 也就是说解出来dp(lenA,lenB)就可以了。 */ int main() { cin>>s>>ss; st+=s,ed+=ss; n=st.size(),m=ed.size(); for(int i=1;i<=n;i++) dp[i][0]=i; for(int j=1;j<=m;j++) dp[0][j]=j; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) /* 对于转移方程: 对于减操作: 可以看作是删除当前A的第i位,变成 (i-1,j),然后操作次数+1; 通俗的说就是i位置如果选择减,那么就会变成(i-1,j)的状态,然后操作次数+1; 对于加操作: 同理可以看看作,抵消掉B的第j位,我们加的字符一定是与B对应位相等的,所以会变成 (i,j-1)的状态 对于变化操作: 也就是我现在这两位相等了,那我可以直接把这两位删除,如果本来就相等那就不变 */ dp[i][j]=min({dp[i-1][j]+1,dp[i][j-1]+1,dp[i-1][j-1]+(st[i]==ed[j]?0:1)}); cout<<dp[n][m]; return 0; }
//https://www.luogu.com.cn/problem/P4933 //遍历前面i之前的所有状态,然后res用于计数 //O(n2) //f[i][j]为第i位状态,公差为j #include<bits/stdc++.h> #define int long long using namespace std; const int N=40040,mod=998244353,p=20000; int n,h[N],dp[1010][N],mx,res; signed main() { cin>>n; for(int i=1;i<=n;i++) cin>>h[i]; for(int i=1;i<=n;i++){ res++;//他自己算一个 for(int j=1;j<i;j++){ dp[i][h[i]-h[j]+p]+=dp[j][h[i]-h[j]+p]+1;//等差为h[i]-h[j]的状态+1 dp[i][h[i]-h[j]+p]%=mod; res=(res+dp[j][h[i]-h[j]+p]+1)%mod;//把i之前的所有状态都加起来,因为状态更新了 } } cout<<res%mod; return 0; } //O(nv) #include<bits/stdc++.h> using namespace std; const int N=80010,mod=998244353,p=20000; int n,h[200001],dp[1010][N],res,v,v_; vector<int>pos[200001]; int main() { cin>>n; for(int i=1;i<=n;i++) cin>>h[i],pos[h[i]].push_back(i); for(int i=1;i<=n;i++) for(int j=-p;j<=p;j++) dp[i][j+40000]=1; for(int i=1;i<=n;i++){ v_=max(v_,h[i]); for(int j=-v_;j<=h[i];j++){//枚举公差 for(int k=0;k<pos[h[i]-j].size();k++){ int x=pos[h[i]-j][k]; if(x>=i) break;//小小优化,假如当前下标大于等于i,则后面的下标也一定大于i,因此直接退出即可 dp[i][j+40000]+=dp[x][j+40000],dp[i][j+40000]%=mod; res+=dp[x][j+40000],res%=mod; } } } cout<<(res+n)%mod; return 0; }
//https://www.luogu.com.cn/problem/P8625 //树上dp,求连通块的最大值 #include<bits/stdc++.h> #define int long long using namespace std; const int N=2e5+10; int dp[N],n,v[N],res,ans; int e[N],ne[N],h[N],idx; void add(int a,int b) { e[idx]=b,ne[idx]=h[a],h[a]=idx++; } int dfs(int u,int fa) { for(int i=h[u];~i;i=ne[i]){ int j=e[i]; if(j!=fa) dp[u]=max(dp[u],dp[u]+dfs(j,u)); } return dp[u]; } signed main() { cin>>n; memset(h,-1,sizeof h); for(int i=1;i<=n;i++) cin>>v[i],dp[i]=v[i]; for(int i=1;i<n;i++){ int u,v; cin>>u>>v; add(u,v),add(v,u); } res=dfs(1,0); for(int i=1;i<=n;i++) ans=max(dp[i],ans); cout<<ans; return 0; }
//https://www.luogu.com.cn/problem/P2733 //矩阵dp #include<bits/stdc++.h> #define int long long using namespace std; const int N=300; int dx[]={1,0,1},dy[]={0,1,1}; int n,dp[N][N],res,col[N]; signed main() { cin>>n; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) scanf("%1lld",&dp[i][j]); for(int k=2;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(dp[i+1][j]+dp[i+1][j+1]+dp[i][j+1]+dp[i][j]==(k-1)*4) dp[i][j]++,col[k]++; for(int i=2;i<=n;i++) if(col[i]) cout<<i<<' '<<col[i]<<endl; return 0; } //矩阵前缀和 #include<bits/stdc++.h> using namespace std; const int N=300; int dp[N][N],mp[N][N],res,n; int main() { cin>>n; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) scanf("%1d",&mp[i][j]); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) dp[i][j]=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]+mp[i][j]; for(int k=2;k<=n;k++){ res=0; for(int i=k;i<=n;i++){ for(int j=k;j<=n;j++){ if(dp[i][j]-dp[i-k][j]-dp[i][j-k]+dp[i-k][j-k]==k*k) res++; } } if(res==0) return 0; cout<<k<<' '<<res<<endl; } return 0; }
//https://www.luogu.com.cn/problem/P1004 //首先提供二维dp,二维dp的思路为ij表示i行j列时的可以取得最大值 //类似于贪心,先进行第一遍循环,取到最优,然后把第一遍取的数全变为0,再进行第二遍的取 //但是这种方法并不一定是全局的最优解 //0 0 2 3 0 0 0 //0 0 3 0 0 0 0 //0 0 3 0 0 0 0 //0 0 0 0 0 0 0 //0 0 0 0 4 0 0 //0 0 0 0 4 0 0 //0 0 0 0 4 0 0 //如图,走第一遍可得出终点时最大值为20,去掉已经走过的点后图如下: //0 0 0 3 0 0 0 //0 0 0 0 0 0 0 //0 0 0 0 0 0 0 //0 0 0 0 0 0 0 //0 0 0 0 0 0 0 //0 0 0 0 0 0 0 //0 0 2 0 0 0 0 //然后会发现我们无法全部走完,也正符合贪心策略,“只注重眼前的利益”,因此此题使用二维dp绝非正解,上代码: #include<bits/stdc++.h> using namespace std; const int N=10; int dx[]={0,1},dy[]={1,0},n,mp[N][N]; int dp[N][N],res,ans; int main() { cin>>n; int a,b,c; while(cin>>a>>b>>c&&a+b+c>0) mp[a][b]=c; // for(int i=1;i<=n;i++){ // for(int j=1;j<=n;j++) cout<<mp[i][j]<<' '; // cout<<endl; // } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) dp[i][j]=max(dp[i-1][j]+mp[i][j],dp[i][j-1]+mp[i][j]); res=dp[n][n],ans=dp[n][n]; for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ if(dp[i][j]==res&&dp[i][j]!=0) res-=mp[i][j],mp[i][j]=0,i=0,j=0; } } memset(dp,0,sizeof dp); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) dp[i][j]=max(dp[i-1][j]+mp[i][j],dp[i][j-1]+mp[i][j]); cout<<ans+dp[n][n]; return 0; } //正确思路: //四维dp,用ijkl表示思维,ij表示第一遍走的,kl表示第二遍走的,然后进行n^4的dp //如果遇到相同的点就直接减去 #include<bits/stdc++.h> using namespace std; const int N=10; int n,m,dp[N][N][N][N],mp[N][N]; int main() { cin>>n; int a,b,c; while(cin>>a>>b>>c&&a+b+c>0) mp[a][b]=c; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) for(int k=1;k<=n;k++) for(int l=1;l<=n;l++){ dp[i][j][k][l]=max(max(dp[i-1][j][k-1][l],max(dp[i-1][j][k][l-1],dp[i][j-1][k][l-1])),dp[i][j-1][k-1][l])+mp[i][j]+mp[k][l]; if(i==k&&j==l) dp[i][j][k][l]-=mp[k][l]; } cout<<dp[n][n][n][n]; return 0; }
__EOF__

本文作者:Sakurajimamai
本文链接:https://www.cnblogs.com/o-Sakurajimamai-o/p/17789433.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
本文链接:https://www.cnblogs.com/o-Sakurajimamai-o/p/17789433.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
分类:
算法合集 / 动态规划
, 算法合集
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)