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 @ 2024-09-20 21:20  _KidA  阅读(16)  评论(0)    收藏  举报