线性dp
例题一:最长不下降子序列
思路
可以考虑到\(dp[i]=dp[j]+1,(j<i,a[j]<a[i])\),因此可以使用两层\(for\)循环求出答案。考虑到当前点的最优解一定是基于前一个恰好比当前点小的点的值,因此即可每次二分查找已有的序列,若序列末尾都小于了当前点,就更新当前点,反之,则替换第一个比当前点大的数。
Code
#include<bits/stdc++.h>
using namespace std;
int n, a[100005], dp[100005];
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+'0');
}
int main(){
read(n);
for(register int i = 1; i <= n; ++i) read(a[i]);
int tmp = 0;
dp[++tmp] = a[1];
for(register int i = 2; i <= n; ++i){
if(a[i] > dp[tmp]) dp[++tmp] = a[i];
else{
int p = lower_bound(dp+1, dp+tmp+1, a[i])-dp;
dp[p] = a[i];
}
}
write(tmp);
return 0;
}
例题二:最长公共子序列
思路
很容易可以想到\(dp[i][j]=\begin{cases}dp[i-1][j-1]+1,a[i]=b[j]\\\max(dp[i-1][j],dp[i][j-1]),a[i]\neq b[j]\end{cases}\)但是二维的数组以及\(n^2\)的时间复杂度都是不被允许的。所以把他转化为别的问题。
如果我们将串\(A\)编号为\(1,2,\ldots,n\)那么可以得出\(B\)串能与其匹配的就是他的对应编号的最长上升降子序列,。
\(Code\)
#include<bits/stdc++.h>
using namespace std;
int x, b[100001], a[100001], dp[100001];
int n;
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);
a[x] = i;
}
for(register int i = 1; i <= n; ++i) read(b[i]);
memset(dp, 0x3f3f3f3f, sizeof(dp));
int tmp = 0;
dp[0] = 0;
for(register int i = 1; i <= n; ++i){
int l = 0, r = tmp, mid;
if(a[b[i]] > dp[tmp]) dp[++tmp] = a[b[i]];
else{
while(l<r){
mid = l+r >> 1;
if(dp[mid] > a[b[i]]) r = mid;
else l = mid+1;
}
dp[l] = min(a[b[i]],dp[l]);
}
}
write(tmp);
return 0;
}
例题三:\(NOIP2008\)传纸条
思路
数据范围\(n,m\leq 50\)很容易就能想到四维\(dp\),考虑到可能重复的问题,我们可以肯定的是因为路径是不会交叉的,所以一条路径肯定在左下,一条在由上,因此只需要保证\(i<j,p>q\)即可不重不漏。方程为
用\(O(n^4)\)的时空复杂度即可解决。
\(Code\)
#include<bits/stdc++.h>
using namespace std;
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 n, m;
int dp[55][55][55][55];
int a[55][55];
int main(){
read(m), read(n);
for(register int i = 1; i <= m; ++i)
for(register int j = 1; j <= n; ++j)
read(a[i][j]);
for(register int i = 1; i <= m; ++i)
for(register int j = 1; j <= n; ++j)
for(register int p = i+1; p <= m; ++p)
for(register int q = 1; q < j; ++q)
dp[i][j][p][q] = max(dp[i-1][j][p-1][q], max(dp[i-1][j][p][q-1], max(dp[i][j-1][p-1][q], dp[i][j-1][p][q-1])))+a[i][j]+a[p][q];
write(dp[m-1][n][m][n-1]);
return 0;
}
例题四:\(CF837D:Round Subset\)
思路
*什么情况下会有\(0\)。
可以通过\(1\)年级数学知识得知若设该数 \(2\) 的因子个数为 \(two\),\(5\) 的因子个数为 \(five\),可知 \(0\) 的个数为 \(\min(two,five)\)。
如何得到最优解
我们可以设 \(dp[j][k]\) 表示当前已经到了 \(j\) 并且有 \(k\) 个因子 \(5\) 时最大的因子 \(2\) 的个数。
所以,我们可以推出式子:
\(dp[j][k]=max(dp[j][k],dp[j-1][k-five[i]]+two[i])\)
\(i\) 为当前枚举到的数,\(j\) 为以选择个数,\(k\) 为 \(5\) 因子数。
for(register int i = 1; i <= n; ++i)
for(register int j = i; j >= 1; --j)
for(register int p = 6200; p >= five[i]; --p)
dp[j][p] = max(dp[j][p], dp[j-1][p-five[i]]+two[i]);
如何查询
从 \(6200\) 到 \(1\) 查询,\(ans = \max(dp[k][i],i)\),若 \(ans>i\),则直接输出 \(ans\),因为此后 \(i\) 只会越来越小直到找不到就输出 \(ans\)。
Q1:为何 \(p\) 是 \(6200\) -> \(five_i\):有 \(200\) 个数,每个数大概最多是 \(30\) 因子左右,所以最多也不超过 \(6200\)。
Q2:为何 \(p\) 是从大到小枚举:如果从小到大会选多次(可参考完全背包)。
Q3:为何要初始化 \(dp\) 数组:部分状态可能存在非法情况(根本无法到达),如果不 \(memset\) 就会让他更新别的状态,导致错误答案。
\(Code\)
#include<bits/stdc++.h>
using namespace std;
int n, k, ans;
int dp[205][6205];
long long num[205];
int two[205], five[205];
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+'0');
}
int main(){
read(n), read(k);
for(register int i = 1; i <= n; ++i){
read(num[i]); //输入
while(num[i] % 2 == 0){ //循环找two[i]
++two[i];
num[i] /= 2;
}
while(num[i] % 5 == 0){ //循环找five[i]
++five[i];
num[i] /= 5;
}
}
memset(dp, -0x3f, sizeof(dp)); //初始化dp数组
dp[0][0] = 0;
for(register int i = 1; i <= n; ++i)
for(register int j = i; j >= 1; --j)
for(register int p = 6200; p >= five[i]; --p)
dp[j][p] = max(dp[j][p], dp[j-1][p-five[i]]+two[i]);
for(register int i = 6200; i >= 1; --i){
ans = max(ans, min(i, dp[k][i])); //寻找ans
if(ans > i){
write(ans);
return 0;
}
}
write(ans); //输出
return 0;
}