Living-Dream 系列笔记 第79期
P1775
典。
code
#include<bits/stdc++.h> #define int long long using namespace std; const int N=3e2+5; int n,a[N],sum[N],dp[N][N]; signed main(){ ios::sync_with_stdio(0); cin.tie(0); cin>>n; memset(dp,0x3f,sizeof dp); for(int i=1;i<=n;i++) cin>>a[i],sum[i]=sum[i-1]+a[i],dp[i][i]=0; for(int l=2;l<=n;l++){ for(int i=1;i+l-1<=n;i++){ int j=i+l-1; for(int k=i;k<j;k++) dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]); dp[i][j]+=sum[j]-sum[i-1]; } } cout<<dp[1][n]; return 0; }
P1040
很容易发现这玩意是个区间 dp。因为中序遍历是 左-根-右 的,考虑枚举根。
状态:令 \(dp_{i,j}\) 表示区间 \([i,j]\) 的最高加分。
初始:\(dp_{i,i}=a_i,dp_{i,i-1}=1\)。
转移:\(dp_{i,j}=dp_{i,k} \times dp_{k+1,j} + a_k\)。
答案:\(dp_{1,n}\)。
每次转移成功时顺便记录根即可。
code
#include<bits/stdc++.h> #define int long long using namespace std; const int N=35; int n,a[N],dp[N][N],rt[N][N]; void dfs(int l,int r){ if(l>r) return; cout<<rt[l][r]<<' '; dfs(l,rt[l][r]-1); dfs(rt[l][r]+1,r); } signed main(){ ios::sync_with_stdio(0); cin.tie(0); cin>>n; for(int i=1;i<=n;i++) cin>>a[i],dp[i][i]=a[i],dp[i][i-1]=1,rt[i][i]=i; for(int l=2;l<=n;l++){ for(int i=1;i+l-1<=n;i++){ int j=i+l-1; for(int k=i;k<j;k++) if(dp[i][j]<dp[i][k-1]*dp[k+1][j]+a[k]) dp[i][j]=dp[i][k-1]*dp[k+1][j]+a[k],rt[i][j]=k; } } cout<<dp[1][n]<<'\n'; dfs(1,n); return 0; }
P4290
容易发现这是一个区间 dp,因为可以将每个区间拆成两部分,它们分别对应两个字母,这正符合区间 dp 枚举中转点的套路。
考虑设计一个 bool 类型的状态:令 \(dp_{i,j,k}\) 表示区间 \([i,j]\) 可不可以由字母 \(k\) 变换得到。
(因为这题有颜色,所以要多带一个维度)
初始:\(dp_{i,i,s_i}=1\)(\(s\) 为输入的字符串)。
答案:枚举所有颜色 \(k\),检查 \(dp_{1,n,k}\) 即可。
转移:枚举两个颜色 \(k1,k2\),若 \(k1,k2\) 能代替 \(k\) 且满足 \(dp_{i,p,k1}=1\) 且 \(dp_{p+1,j,k2}=1\),则 \(dp_{i,j,k}=1\)(\(p\) 是枚举的中转点)。
code
#include<bits/stdc++.h> using namespace std; const int N=2e2+5,M=4; string s; int n,a[M],mp[N]; char to[N]; bool dp[N][N][M],ok[M][M][M]; int main(){ for(int i=0;i<4;i++) cin>>a[i]; mp['W']=0,to[0]='W'; mp['I']=1,to[1]='I'; mp['N']=2,to[2]='N'; mp['G']=3,to[3]='G'; for(int c=0;c<4;c++){ for(int i=1;i<=a[c];i++){ char c1,c2; cin>>c1>>c2; ok[c][mp[c1]][mp[c2]]=1; } } cin>>s,n=s.size(),s='#'+s; for(int i=1;i<=n;i++) dp[i][i][mp[s[i]]]=1; for(int l=1;l<=n;l++){ for(int i=1;i+l-1<=n;i++){ int j=i+l-1; for(int k=i;k<j;k++){ for(int c=0;c<4;c++){ for(int c1=0;c1<4;c1++){ for(int c2=0;c2<4;c2++){ if(ok[c][c1][c2]&&dp[i][k][c1]&&dp[k+1][j][c2]){ dp[i][j][c]=1; } } } } } } } bool f=0; for(int c=0;c<4;c++){ if(dp[1][n][c]){ cout<<to[c]; f=1; } } if(!f){ cout<<"The name is wrong!"; } return 0; }
P8675
由规则 \(2\) 可以看出这是一道区间 dp。
令 \(dp_{L,i,j}\) 表示第 \(L\) 层放了 \([i,j]\) 的方案数。
答案是 \(\sum dp_{L,i,j}\)(不要求放满 \(n\) 层)。
初始化一下第一层就可以开始转移了。
在转移第 \(L\) 层时,我们必须保证 \(L-1\) 层能托住他,因此我们只能从 \(L-1\) 层的 \(1 \sim i\) 以及 \(j \sim m\) 的 dp 值转移过来。
朴素做法是直接求一遍和,显然这样太 man。
我们把 \(L-1\) 层的 dp 值想像成一个矩阵,我们要求的就是 \(1 \sim i\) 行以及 \(j \sim m\) 列的总和,直接上二维前缀和维护即可。
至于规则 \(3\) 很好判,再对每层搞个一维前缀和就行了。
注意需要倒过来 dp。
code
#include<bits/stdc++.h> #define int long long using namespace std; const int N=1e2+5; const int MOD=1e9+7; int n,m; char c[N][N]; int dp[N][N][N],sum[N][N],sumx[N][N][N]; void upd(int L){ for(int i=1;i<=m;i++) for(int j=i;j<=m;j++) sumx[L][i][j]=(((sumx[L][i-1][j]+sumx[L][i][j-1])%MOD-sumx[L][i-1][j-1]+MOD)%MOD+dp[L][i][j])%MOD; } signed main(){ ios::sync_with_stdio(0); cin.tie(0); cin>>n>>m; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cin>>c[i][j],sum[i][j]=sum[i][j-1]+(c[i][j]=='X'); for(int i=1;i<=m;i++) for(int j=i;j<=m;j++) if(sum[n][j]==sum[n][i-1]) dp[n][i][j]=1; for(int L=n-1;L>=1;L--){ upd(L+1); for(int l=1;l<=m;l++){ for(int i=1;i+l-1<=m;i++){ int j=i+l-1; if(sum[L][j]!=sum[L][i-1]) continue; dp[L][i][j]=(dp[L][i][j]+(sumx[L+1][i][m]-sumx[L+1][i][j-1]+MOD)%MOD)%MOD; } } } int ans=1; for(int L=1;L<=n;L++) for(int i=1;i<=m;i++) for(int j=i;j<=m;j++) ans=(ans+dp[L][i][j])%MOD; cout<<(ans+MOD)%MOD; return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效