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;
}
posted @   _XOFqwq  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示