Living-Dream 系列笔记 第49期

Posted on 2024-03-09 22:01  _XOFqwq  阅读(4)  评论(0编辑  收藏  举报

T1

\(dp_{i,j}\) 表示卖出区间 \([i,j]\) 能获得的最大价值。

显然答案为 \(dp_{1,n}\)

因为只能卖 \(i\) / \(j\),所以有转移:

\[dp_{i,j}=\max(dp_{i+1,j}+v_i \times (n-len+1),dp_{i,j-1}+v_j \times (n-len+1)) \]

初始:\(dp_{i,i}=v_i \times n\),其余为 \(- \infty\)

code
#include<bits/stdc++.h>
using namespace std;

const int N=2e3+5;
int n;
int v[N];
int dp[N][N];

int main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>v[i];
	memset(dp,0xcf,sizeof(dp));
	for(int i=1;i<=n;i++) dp[i][i]=v[i]*n;
	for(int i=2;i<=n;i++){
		for(int j=1;j+i-1<=n;j++){
			int s=j,e=j+i-1;
			dp[s][e]=max(dp[s][e-1]+v[e]*(n-i+1),dp[s+1][e]+v[s]*(n-i+1));
		}
	}
	cout<<dp[1][n];
	return 0;
}

T2

我们注意到,对于字符串中的一个非对称位置 \(i\)

在对应位置添加 \(s_i\) 或删除 \(s_i\) 是等价的。

于是对于每个字符,它的操作代价即为 \(\min(\)添加代价\(,\)删除代价\()\)

然后这题显然是个区间 dp。

因此有状态:令 \(dp_{i,j}\) 表示 将 \([i,j]\) 改为回文串的最小代价,答案为 \(dp_{1,m}\)

接着我们发现这题显然不能用枚举中转点的套路,

因为即使 \([i,k]\)\([k+1,j]\) 均为回文串,也不能保证 \([i,j]\) 为回文串。

于是我们考虑对端点进行转移,

具体来说,我们有:

\[dp_{i,j}=\max(dp_{i+1,j}+v_{s_i},dp_{i,j-1}+v_{s_j}) \]

因为我们是从短到长枚举区间,所以此转移的正确性可以保证。

同时,若 \(s_i=s_j\),则继承中间的 \(dp_{i+1,j-1}\) 即可。

在这种情况下,若区间长度 \(i \le 2\),则直接令 \(dp_{i,j}=0\) 即可。

code
#include<bits/stdc++.h>
using namespace std;

const int N=2e3+5;
int n,m;
string t;
int v[N];
int dp[N][N];

int main(){
	cin>>n>>m>>t,t='#'+t;
	for(int i=1;i<=n;i++){
		char c; int x,y;
		cin>>c>>x>>y;
		v[c]=min(x,y);
	}
	memset(dp,0x3f,sizeof(dp));
	for(int i=1;i<=m;i++) dp[i][i]=0;
	for(int i=2;i<=m;i++){
		for(int j=1;j+i-1<=m;j++){
			int s=j,e=j+i-1;
			if(t[s]==t[e]){
				if(i==2) dp[s][e]=0;
				else dp[s][e]=dp[s+1][e-1];
			}
			dp[s][e]=min(dp[s][e],min(dp[s][e-1]+v[t[e]],dp[s+1][e]+v[t[s]]));
		}
	}
	cout<<dp[1][m];
	return 0;
}

T3

与上题类似,此处略。

T4

一眼区间 dp。

\(dp_{i,j}\) 表示 \([i,j]\) 能折叠出的最小长度。

答案即为 \(dp_{1,n}\)\(n\) 表示 \(S\) 的长度)。

初始状态显然有 \(dp_{i,i}=1\),其余为 \(\infty\)

对于转移,我们进行分类讨论:

  • 若不折叠,直接拼接,则按照枚举中转点套路即可。有转移:

\[dp_{i,j}=\min(dp_{i,j},dp_{i,k}+dp_{k+1,j}) \]

  • 否则若进行折叠,则重复部分的长度必为当前区间长度 \(len\) 的因数。

    于是我们从 \(1\)\(\frac{len}{2}\)(因为枚举到 \(len\) 就相当于情况 1)去枚举 \(len\) 的因数 \(l\)

    并检查其是否满足要求。若满足,则有转移:

\[dp_{i,j}=dp_{i,i+l-1}+c(\frac{len}{l})+2 \]

(其中 \(c(\frac{len}{l})\) 表示 \(\frac{len}{l}\) 的位数,即题中的 \(X\)\(2\) 表示两个括弧)

直接转移即可。

code
#include<bits/stdc++.h>
using namespace std;

const int N=1e2+5;
int m;
string t;
int v[N];
int dp[N][N];

bool check(int l,int r,int k){
	string o="";
	for(int i=l;i<=r;i+=k){
		if(i==l) o=t.substr(i,k);
		else
			if(t.substr(i,k)!=o) return 0;
	}
	return 1;
}
int c(int x){
	int p=0;
	while(x) p++,x/=10;
	return p;
}

int main(){
	cin>>t,t='#'+t,m=t.size()-1;
	memset(dp,0x3f,sizeof(dp));
	for(int i=1;i<=m;i++) dp[i][i]=1;
	for(int i=2;i<=m;i++){
		for(int j=1;j+i-1<=m;j++){
			int s=j,e=j+i-1;
			for(int k=s;k<e;k++) dp[s][e]=min(dp[s][e],dp[s][k]+dp[k+1][e]);
			for(int l=1;l<=i/2;l++)
				if(!(i%l)&&check(s,e,l))
					dp[s][e]=min(dp[s][e],dp[s][s+l-1]+c(i/l)+2);
		}
	}
	cout<<dp[1][m];
	return 0;
}

作业 T1

\(dp_{i,j}\) 表示删去区间 \([i,j]\) 能获得的最大价值,答案即为 \(dp_{1,n}\)

显然有初始状态令 \(dp_{i,i}=x_i\),其余为 \(-\infty\)

对于 \([i,j]\),我们可以直接全删,于是先令 \(dp_{i,j}=\lvert x_i-x_j \rvert \times (j-i+1)\)

然后按枚举中转点套路转移即可。

code
#include<bits/stdc++.h>
using namespace std;

const int N=1e2+5;
int n,a[N],dp[N][N];

int main(){
	cin>>n,memset(dp,0xcf,sizeof(dp));
	for(int i=1;i<=n;i++) cin>>a[i],dp[i][i]=a[i];
	for(int i=2;i<=n;i++){
		for(int j=1;j+i-1<=n;j++){
			int s=j,e=j+i-1;
			dp[s][e]=abs(a[s]-a[e])*(e-s+1);
			for(int k=s;k<e;k++)
				dp[s][e]=max(dp[s][e],dp[s][k]+dp[k+1][e]);
		}
	}
	cout<<dp[1][n];
	return 0;
}

作业 T2

因为题中只含有两端点操作,所以考虑对端点进行转移。

具体地:

\(dp_{i,j,0}\) 表示区间 \([i,j]\)\(i\) 从左边进的的初始队列方案数;

\(dp_{i,j,0}\) 表示区间 \([i,j]\)\(j\) 从右边进的的初始队列方案数。

答案即为 \((dp_{1,n,0}+dp_{1,n,1}) \bmod 19650827\)

然后显然有初始状态令 \(dp_{i,i,0}=1\)

这里默认第一个人从左进,避免重复计算。

接着考虑到第 \(i\) 人从左进时,它前面的人只可能为 \(i+1\)\(j\)

因此它必须满足 \(h_i<h_{i+1}\)\(h_i<h_j\) 才能进行转移,

易得此时转移方程:

\[dp_{i,j,0}=dp_{i,j,0}+dp_{i+1,j,0}(h_i<h_{i+1}) \]

\[dp_{i,j,0}=dp_{i,j,0}+dp_{i+1,j,1}(h_i<h_j) \]

同理,易得第 \(j\) 人从右进时的转移方程:

\[dp_{i,j,1}=dp_{i,j,1}+dp_{i,j-1,0}(h_j>h_{j-1})\\ \]

\[dp_{i,j,1}=dp_{i,j,1}+dp_{i,j-1,1}(h_j>h_i) \]

然后直接转移即可。注意取模。

code
#include<bits/stdc++.h>
using namespace std;

const int N=1e3+5,MOD=19650827;
int n,a[N],dp[N][N][2];

int main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i],dp[i][i][0]=1;
	for(int i=2;i<=n;i++){
		for(int j=1;j+i-1<=n;j++){
			int s=j,e=j+i-1;
			if(a[s]<a[s+1]) dp[s][e][0]+=dp[s+1][e][0];
			if(a[s]<a[e]) dp[s][e][0]+=dp[s+1][e][1];
			if(a[e]>a[e-1]) dp[s][e][1]+=dp[s][e-1][1];
			if(a[e]>a[s]) dp[s][e][1]+=dp[s][e-1][0];
			dp[s][e][0]%=MOD,dp[s][e][1]%=MOD;
		}
	}
	cout<<(dp[1][n][0]+dp[1][n][1])%MOD;
	return 0;
}