区间dp
区间 \(dp\) 一般还是有很明显的提示,时间复杂度 \(O(n^3)\),因此一般 \(n\leq 500\)。实现一般也很套路,外层循环枚举区间长度 \(len\);中层枚举始端点 \(i\),并得出末端点 \(j=i+len-1\);内层枚举 \(k,i\leq k<j\);写状态转移方程。
例题一:石子合并
思路
最简单的区间 \(dp\),以求 \(max\) 而言 \(f_{i,j}=\max(f_{i,j},f_{i,k}+f_{k+1,j})\)。
\(Code\)
#include<bits/stdc++.h>
using namespace std;
int n, v[210], f1[210][210], f2[210][210], a[210], x;
template<typename T>
inline void read(T&x){
x = 0; char q; bool f = 1;
while(!isdigit(q = getchar())) if(q == '-') f = 0;
while(isdigit(q)){
x = (x<<1) + (x<<3) + (q^48);
q = getchar();
}
x = f?x:-x;
}
template<typename T>
inline void write(T x){
if(x < 0){
putchar('-');
x = -x;
}
if(x > 9) write(x/10);
putchar(x%10^48);
}
int main(){
read(n);
for(register int i = 1; i <= n; ++i) read(a[i]);
for(register int i = 1; i <= n; ++i) v[i] = v[i-1] + a[i];
for(register int i = n+1; i <= 2*n; ++i) v[i] = v[i-1] + a[i-n];
for(register int len = 2; len <= 2*n; ++len)
for(register int i = 1; i <= 2*n-len+1; ++i){
int j = i+len-1;
f1[i][j] += f1[i+1][j];
f2[i][j] += f2[i+1][j];
for(register int k = i; k <= j-1; k++){
f1[i][j] = min(f1[i][j], f1[i][k]+f1[k+1][j]);
f2[i][j] = max(f2[i][j], f2[i][k]+f2[k+1][j]);
}
f1[i][j] += v[j]-v[i-1];
f2[i][j] += v[j]-v[i-1];
}
int minn = 0x3f3f3f3f, maxn = -0x3f3f3f3f;
for(register int i = 1; i <= n; ++i){
if(minn > f1[i][i+n-1]) minn = f1[i][i+n-1];
if(maxn < f2[i][i+n-1]) maxn = f2[i][i+n-1];
}
write(minn), puts(""), write(maxn);
return 0;
}
例题二:248 G
思路
还是一样的套路,\(f_{i,j}=\max(f_{i,j},f_{i,k}+1),f_{i,k}=f_{k+1,j}\neq0\)。
\(Code\)
#include<bits/stdc++.h>
using namespace std;
int n;
int x, f[250][250];
int ans = 0;
template<typename T>
inline void read(T&x){
x = 0; char q; bool f = 1;
while(!isdigit(q = getchar())) if(q == '-') f = 0;
while(isdigit(q)){
x = (x<<1) + (x<<3) + (q^48);
q = getchar();
}
x = f?x:-x;
}
template<typename T>
inline void write(T x){
if(x < 0){
putchar('-');
x = -x;
}
if(x > 9) write(x/10);
putchar(x%10^48);
}
int main(){
read(n);
for(register int i = 1; i <= n; ++i) read(x), f[i][i] = x;
for(register int len = 2; len <= n; ++len)
for(register int i = 1; i <= n-len+1; ++i){
int j = i+len-1;
for(register int k = i; k < j; ++k){
if(f[i][k] == f[k+1][j] && f[i][k] != 0){
f[i][j] = max(f[i][j], f[i][k]+1);
ans = max(ans, f[i][k]+1);
}
}
}
write(ans);
return 0;
}
例题三:涂色
思路
状态转移方程稍微麻烦一点,但是作法没有实质上的改变。
\[f_{i,j}=
\begin{cases}
1,i=j\\
\min(f_{i,j-1},f_i+1,j),i\neq j,a_i=a_j\\
\min(f_{i,j},f_{i,k}+f_{k+1,j}),i\neq j,a_i\neq a_j
\end{cases}
\]
\(Code\)
#include<bits/stdc++.h>
using namespace std;
char a[55];
int f[55][55];
template<typename T>
inline void read(T&x){
x = 0; char q; bool f = 1;
while(!isdigit(q = getchar())) if(q == '-') f = 0;
while(isdigit(q)){
x = (x<<1) + (x<<3) + (q^48);
q = getchar();
}
x = f?x:-x;
}
template<typename T>
inline void write(T x){
if(x < 0){
putchar('-');
x = -x;
}
if(x > 9) write(x/10);
putchar(x%10^48);
}
int main(){
memset(f, 0x3f3f3f3f, sizeof(f));
scanf("%s", a+1);
int n = strlen(a+1);
for(register int i = 1; i <= n; ++i) f[i][i] = 1;
for(register int len = 2; len <= n; ++len)
for(register int i = 1; i <= n-len+1; ++i){
int j = i+len-1;
if(a[i] == a[j]){
f[i][j] = min(f[i][j-1], f[i+1][j]);
continue;
}
for(register int k = i; k < j; ++k) f[i][j] = min(f[i][j], f[i][k]+f[k+1][j]);
}
write(f[1][n]);
return 0;
}