某 dp 题单题解 0116

Pengzt 的博客 / 2024-01-22 / 原文

A. 甲虫
Problem

给定一个数轴,现在有一只甲虫在原点。有 \(n\) 滴露水,坐标分别为 \(x_1,x_2,\dots,x_n\),每滴露水有 \(m\) 的价值。甲虫每秒可以移动一步,它在 \(t\) 时刻所能获得的露水的价值为 \(\max(m-t,0)\)。求甲虫最多能喝到多少水。

\(0\le n\le 300,1\le m\le 10^6,|x_i|\le 10^4\),保证 \(x_i\) 互不相同。

\(4s,15.63MB\)

Sol

经典的区间 dp。考虑 \(f_{i,j,0/1}\) 表示吃完 \([i,j]\) 的露水,在 \(i/j\) 的最大值。然后发现这东西还要考虑时间,类似于 ABC219H,考虑费用提前计算。这样的话还要枚举吃多少露水,答案转移时更新即可。

不能单独记录一个 \(g_{i,j,0/1}\) 的原因是可能存在一段 \([i,j]\) 的最优策略花的时间更长,却需要尽快的去取旁边的露水,就是这个 \(g\) 只保证了局部的最优性,所以是错的。

时间复杂度:\(\mathcal{O}(n^3)\)

code
#include<bits/stdc++.h>
using namespace std;
#define Fin(file) freopen(file,"r",stdin)
#define Fout(file) freopen(file,"w",stdout)
#define L(i,j,k) for(int i=(j);i<=(k);++i)
#define R(i,j,k) for(int i=(j);i>=(k);--i)
#define ll long long
int n,m;
int a[310];
ll f[310][310][2];
int main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	cin>>n>>m;
	ll ans=0;
	L(i,1,n)cin>>a[i];
	sort(a+1,a+n+1);
	L(len,1,n){
		memset(f,0,sizeof(f));
		L(i,1,n)ans=max(ans,f[i][i][0]=f[i][i][1]=m-abs(a[i])*len);
		L(i,2,len)L(l,1,n-i+1){
			int r=l+i-1,tmp=len-r+l;
			f[l][r][0]=max(f[l+1][r][0]-abs(a[l+1]-a[l])*tmp,f[l+1][r][1]-abs(a[r]-a[l])*tmp)+m;
			f[l][r][1]=max(f[l][r-1][0]-abs(a[r]-a[l])*tmp,f[l][r-1][1]-abs(a[r]-a[r-1])*tmp)+m;
			ans=max(ans,max(f[l][r][0],f[l][r][1]));
		}
	}
	cout<<ans<<"\n";
	return 0;
}
B. 粉刷匠
Problem

\(n\) 条木板,每条木板有 \(m\) 个格子,每个格子需要被涂成红色或蓝色,初始时没有颜色。每次操作可以给一条木板的某一段连续的格子上色,同一个格子不能被多次上色。如果只能操作 \(T\) 次,求最多能正确粉刷的格子数量。

\(1\le n,m\le 50,0\le T\le 2500\)

\(1s,125MB\)

Sol

发现每一条木板之间互不影响,于是先只考虑一条木板。

定义 \(f_{i,j}\) 表示前 \(i\) 个格子,操作 \(j(j>0)\) 次的最大数量。则有 \(f_{i,j}=\max\limits_{k<i}(f_{i-1,j},f_{k,j-1}+\max(cnt(k+1,i,0),cnt(k+1,i,1)))\),其中 \(cnt(l,r,v)\) 表示 \([l,r]\) 中,\(v\) 出现了多少次。这里,红色和蓝色分别对应 \(0/1\)

然后多条木板的话就很简单了,再上一个背包就行了。记 \(v_{i,j}\) 表示第 \(i\) 条木板操作 \(j\) 次的最大数量,\(g_{i,j}\) 表示前 \(i\) 条木板操作 \(j\) 次的最大数量。则有 \(g_{i,j}=\max\limits_{k<j}\{g_{i-1,k}+v_{i,j-k}\}\)。显然操作次数越多越好,所以最后答案就是 \(g_{n,T}\)

若将 \(n,m\) 视为同级,则时间复杂度为 \(\mathcal{O}(n^4)\)

Code
#include<bits/stdc++.h>
using namespace std;
#define Fin(file) freopen(file,"r",stdin)
#define Fout(file) freopen(file,"w",stdout)
#define L(i,j,k) for(int i=(j);i<=(k);++i)
#define R(i,j,k) for(int i=(j);i>=(k);--i)
int n,m,c;
int a[55][55],s[55][55],f[55][55],g[55][2510],h[55][55];
int main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	cin>>n>>m>>c;
	L(i,1,n)L(j,1,m){
		char c;cin>>c;
		a[i][j]=c-'0';
		s[i][j]=a[i][j]+s[i][j-1];
	}
	L(x,1,n){
		memset(f,0,sizeof(f));
		f[1][1]=1;
		L(i,2,m)L(j,1,i)L(k,0,i-1){
			int v=s[x][i]-s[x][k];
			if(!a[x][i])v=i-k-v;
			f[i][j]=max(f[i][j],f[k][j-1]+v);
		}
		L(i,1,m)L(j,i,m)h[x][i]=max(h[x][i],f[j][i]);
	}
	L(i,1,n)L(j,1,min(i*m,c))L(x,0,min(j,m))g[i][j]=max(g[i][j],g[i-1][j-x]+h[i][x]);
	int ans=0;
	L(i,1,c)ans=max(ans,g[n][i]);
	cout<<ans<<"\n";
	return 0;
}
C. Don't Be a Subsequence
Problem

输入一个只有小写字母构成的字符串 \(S\),求不是它的子序列的最短串。如果有多个,输出字典序最小的。

\(1\le |S|\le 2\times 10^5\)

\(2s,256MB\)

Sol

定义 \(f_i\) 表示只考虑前 \(i\) 个时最短串的长度。则有 \(f_i=1+\min\limits_{c=0}^{25}\{ f_{pre_{i,c}-1}\}\)\(pre_{i,c}\) 表示第 \(i\) 个位置之前的最靠后的是 \(c\) 的位置,这里的 \(c\) 指的是 第几个小写字母。

但是这个题还要求字典序最小,如果把 \(f\) 数组变为 string 的话是会 MLE 的,于是就只能枚举 \(T\) 的第 \(i\) 位能否取某一个字符的话,但这个并不好判断,因为不知道取 \(i\) 的时候能否取到最短。所以这个时候倒序 dp 就很显然了,令 \(f_{i}\) 表示只考虑后 \(i\) 个时最短串的长度,则有 \(f_{i}=1+\min\limits_{c=0}^{25}\{f_{nxt_{i,c}+1}\}\)。方案反过来求就行了,这个可以通过逆推得到。

时空复杂度:\(\mathcal{O}(n|\Sigma|)\)\(|\Sigma|\) 表示字符集大小,这里是 \(26\)

Code
#include<bits/stdc++.h>
using namespace std;
#define Fin(file) freopen(file,"r",stdin)
#define Fout(file) freopen(file,"w",stdout)
#define L(i,j,k) for(int i=(j);i<=(k);++i)
#define R(i,j,k) for(int i=(j);i>=(k);--i)
int n;
int f[200010],nxt[200010][26];
string s;
int main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	cin>>s;n=s.size();
	s=" "+s;
	R(i,n,1)L(j,0,25)nxt[i][j]=(s[i]-'a'==j)?i:nxt[i+1][j];
	memset(f,0x3f,sizeof(f));
	f[n+1]=1;
	R(i,n,1)L(j,0,25)if(!nxt[i][j])f[i]=1;else f[i]=min(f[i],f[nxt[i][j]+1]+1);
	int now=1,ans=f[1];
	while(ans>1){
		L(i,0,25)if(f[now]==f[nxt[now][i]+1]+1){
			cout<<(char)(i+'a');
			now=nxt[now][i]+1;
			break;
		}
		--ans;
	}
	L(i,0,25)if(!nxt[now][i]){cout<<(char)(i+'a');break;}
	cout<<"\n";
	return 0;
}
D. Ribbons on Tree
Sol
Code
E. Yes or No
Sol
Code
F. ~K Perm Counting
Sol
Code
G. Chase
Sol
Code
H. 移动金币
Sol
Code
I. 组合数问题
Sol
Code
J. 连珠线
Sol
Code
K. 集合选数
Sol
Code
L. 潜入行动
Sol
Code
M. 随机树
Sol
Code
N. HOT-Hotels 加强版
Sol
Code
O. 划分
Sol
Code
P. 股票交易
Sol
Code
Q. On the Bench
Sol
Code
R. Beautiful numbers
Sol
Code
S. New Year and Original Order
Sol
Code
T. Vasya and Big Integers
Sol
Code
U. Helping People
Sol
Code
V. Array GCD
Sol
Code
W. Sandy and Nuts
Sol
Code
X. Yet Another Minimization Problem
Sol
Code
Y. Good Contest
Sol
Code
Z. Clusterization Counting
Sol
Code