20250906 - 线性dp 总结
前言
关于 dp,我今天已经死了!!!
线性 dp
线性 dp 都可以转换成有向无环图(DAG)。所以,一般的线性 dp 都可以通过图论的方法来写。
关于 dp:
最优子结构:即动态规划中的“状态”,可以通过已知状态转移出未知状态,最终确定答案。
寻找状态之间的关系,即转移方程。
无后效性:已经确定的状态,后续不会再被影响。
dp 就是要多做题!!!!!!!
例题(就不用二级标题了):
A - 最大差值
可以发现,减数越小,被减数越大,这样的结果才会大!
呃呃呃,为什么 scanf \(RE\) 了呢?
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
#define int ll
#define DEBUG
typedef pair<int,int> PII;
const int MAXN = 1e5 + 7;
const int INF = 0x3f3f3f3f3f3f3f3f;
const int MOD = 1e9 + 7;
int n;
ll minx;
ll ans(-INF);
void solve(){
scanf("%lld%lld",&n,&minx);
for(int i = 2;i <= n;i++){
ll tmp;
// scanf("%d",tmp);
cin >> tmp;
ans = max(ans,tmp - minx);
minx = min(minx,tmp);
}
printf("%lld\n",ans);
return;
}
int t;
signed main(){
t = 1;
while(t--){
solve();
}
return 0;
}
B - Frog 1
直接看 \(i-1\) 个和 \(i-2\) 个的哪个大再加上只就好了!
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
#define int int
#define DEBUG
typedef pair<int,int> PII;
const int MAXN = 1e5 + 7;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
int n;
array<int,MAXN>h,dp;
void solve(){
scanf("%d",&n);
for(int i = 1;i <= n;i++){
scanf("%d",&h[i]);
}
dp[2] = abs(h[1] - h[2]);
for(int i = 3;i <= n;i++){
dp[i] = min(dp[i - 1] + abs(h[i] - h[i-1]),dp[i - 2] + abs(h[i] - h[i-2]));
}
printf("%d\n",dp[n]);
return;
}
int t;
int main(){
t = 1;
while(t--){
solve();
}
return 0;
}
C - Frog 2
额,就是上面的题加了一个 k 次!
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
#define int int
#define DEBUG
typedef pair<int,int> PII;
const int MAXN = 1e5 + 7;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
int n,k;
array<int,MAXN>h,dp;
void solve(){
scanf("%d%d",&n,&k);
for(int i = 1;i <= n;i++){
scanf("%d",&h[i]);
}
memset(dp.begin(),0x3f,sizeof(dp));
dp[1] = 0;
for(int i = 2;i <= n;i++)
for(int j = max(1,i-k);j < i;j++)
dp[i] = min(dp[i],dp[j] + abs(h[i]-h[j]));
printf("%d\n",dp[n]);
}
int t;
int main(){
t = 1;
while(t--){
solve();
}
return 0;
}
D - Vacation
设 \(dp(i,j)\) \((j \in \text{\{0,1,2\}})\) 为第 i 天选择(海里游泳,山上捉虫子,家做作业)的最大快乐值,直接转移,但不能转移非法的状态!
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
#define int int
#define DEBUG
typedef pair<int,int> PII;
const int MAXN = 1e5 + 7;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
int n;
array<array<int,3>,MAXN>dp;
array<array<int,3>,MAXN>game;
void solve(){
scanf("%d",&n);
for(int i = 1;i <= n;i++){
for(int j = 0;j < 3;j++){
scanf("%d",&game[i][j]);
}
}
for(int i = 1;i <= n;i++){
dp[i][0] = max(dp[i-1][1],dp[i-1][2]) + game[i][0];
dp[i][1] = max(dp[i-1][0],dp[i-1][2]) + game[i][1];
dp[i][2] = max(dp[i-1][0],dp[i-1][1]) + game[i][2];
}
printf("%d\n",max({dp[n][0],dp[n][1],dp[n][2]}));
return;
}
int t;
int main(){
t = 1;
while(t--){
solve();
}
return 0;
}
E - LCS
额,这道题画了半天图!
首先简单的 \(O(n^2)\) LCS,然后倒着转移;
LCS 伪代码:
for i 1 ~ n:
for j 1 ~ m:
if s[i] == t[j]:
dp[i][j] = max(dp[i][j],dp[i-1][j-1] + 1);
else
dp[i][j] = max(dp[i-1][j],dp[i][j-1])
可以看出,如果 \(s[i] == t[j]\),就可以看 i - 1 和 j - 1;
否则就看 $dp[i][j] $ 是否等于 \(dp[i][j-1]\),如果是,就转移到 j - 1,否则转移到 i - 1。
输出即可!
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
#define int int
#define DEBUG
typedef pair<int,int> PII;
const int MAXN = 3000 + 7;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
char s[MAXN],t[MAXN],ans[MAXN];
array<array<int,MAXN>,MAXN>f;
int n,m;
void solve(){
scanf("%s%s",s + 1,t + 1);
n = strlen(s + 1);
m = strlen(t + 1);
for(int i = 1;i <= n;i++){
for(int j = 1;j <= m;j++){
f[i][j] = max(f[i-1][j],f[i][j-1]);
if(s[i] == t[j])
f[i][j] = max(f[i-1][j-1] + 1,f[i][j]);
}
}
int i = n,j = m;
while(f[i][j] > 0){
if(s[i] == t[j]){
ans[f[i][j]] = s[i];
i--,j--;
}else{
if(f[i-1][j] == f[i][j]){
i--;
}else{
j--;
}
}
}
printf("%s",ans + 1);
return;
}
int main(){
int x = 1;
while(x--){
solve();
}
return 0;
}
F - Grid 1
简单 dp!
当前位置的上面或左边如果是 .
,就加上他,否则就不加。
额,不知为什么,每条路径会出现 2 次,还要除以二,结果逆元写挂了!
我又想,只要当前位置是 .
,他肯定可以从上面或左边走过来,在统计一下!
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
#define int ll
#define DEBUG
typedef pair<int,int> PII;
const int MAXN = 1000 + 7;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
int h,w;
array<array<int,MAXN>,MAXN>dp;
char s[MAXN][MAXN];
ll pows(ll a,ll b,ll mod){
ll res = 1;
a %= mod;
while(b > 0){
if(b & 1){
res *= a,res %= mod;
}
a = a * a;
a %= mod;
b >>= 1;
}
return res;
} // 写了个逆元
void solve(){
scanf("%lld%lld",&h,&w);
for(int i = 1;i <= h;i++){
scanf("%s",s[i] + 1);
}
dp[0][1] = 1;
for(int i = 1;i <= h;i++){
for(int j = 1;j <= w;j++){
if(s[i][j] == '.')
dp[i][j] = (dp[i-1][j]+dp[i][j-1]) % MOD;
}
}
printf("%lld\n",dp[h][w]);
return;
}
int t;
signed main(){
t = 1;
while(t--){
solve();
}
return 0;
}
G - 合唱队形
简单 (LIS)!!!
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
#define int int
#define DEBUG
typedef pair<int,int> PII;
const int MAXN = 1e5 + 7;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
int n;
array<int,MAXN>a,f,f1;
void solve(){
cin >> n;
for(int i = 1;i <= n;i++)
cin >> a[i];
// a[++n] = 0x3f3f3f3f;
for(int i = 1;i <= n;i++){
f[i] = 1;
for(int j = 1;j < i;j++){
if(a[j] < a[i])
f[i] = max(f[i],f[j]+1);
}
}
int ans = 0;
for(int i = n;i >= 1;i--){
f1[i] = 1;
for(int j = n + 1;j > i;j--){
if(a[j] < a[i])
f1[i] = max(f1[i],f1[j]+1);
}
for(int i = n;i >= 1;i--){
ans = max(ans,f[i]+f1[i]-1);
}
}
cout << n - ans << endl;
return;
}
int t;
int main(){
t = 1;
while(t--){
solve();
}
return 0;
}
H - 打鼹鼠
首先立马想到四连通,额,\(1 \le n \le 10^3,1 \le m \le 10^4\),会 MLE 加 TLE!
所以我们要另寻捷径。我们换一种思路,设 \(f_i\) 为抓鼹鼠序列以 i 结尾时我们最多能抓多少鼹鼠。
所以转移就是
好像 LIS 啊,但是限制条件却是到他的距离不能超过他们之间的时间,所以代码就是:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
#define int int
#define DEBUG
typedef pair<int,int> PII;
const int MAXN = 1e4 + 7;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
int n,m;
int tim[MAXN][3];// stl 会超时
int dp[MAXN];
int dist(int ax,int ay,int bx,int by){
return abs(ax - bx) + abs(ay - by);
}
void solve(){
scanf("%d%d",&n,&m);
for(int i = 1;i <= m;i++){
for(int j = 0;j < 3;j++)
scanf("%d",&tim[i][j]);
}
for(int i = 1;i <= m;i++){
dp[i] = 1;
for(int j = 1;j < i;j++){
if(dist(tim[i][1],tim[i][2],tim[j][1],tim[j][2]) <= abs(tim[i][0] - tim[j][0])){
dp[i] = max(dp[i],dp[j] + 1);
}
}
}
int ans = 0;
for(int i = 1;i <= m;i++)
ans = max(ans,dp[i]);
printf("%d\n",ans);
return;
}
int t;
int main(){
t = 1;
while(t--){
solve();
}
return 0;
}
坑点:stl 会超时!!!!!
后记:n 啥用都没有!!!
I - 两个排列的最长公共子序列
LCS 我会!!!结果看数据范围 $ n \le 10^5 $。
\(O(n^2)\) 肯定是不行了,怎么办呢???
由于不知道二分做法,但是会一个好东西 : 树状数组
!!!
可以发现
A_EX : 1 2 5 4 6
A_tag: 1 2 3 4 5
B_Ex : 1 6 5 3 2
B_to_A_tag:
1 5 3 0 2
这样,公共子序列就被转换成了(LIS)了!!!
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
#define int int
#define DEBUG
typedef pair<int,int> PII;
const int MAXN = 1e5 + 7;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
array<int,MAXN>c,a,b,mp;
int n;
int lowbit(int x){
return x & -x;
}
void modify(int x,int val){
while(x <= n){
c[x] = max(c[x],val);
x += lowbit(x);
}
}
int get_sum(int x){
int res = 0;
while(x > 0){
res = max(res,c[x]);
x -= lowbit(x);
}
return res;
}
void solve(){
scanf("%d",&n);
for(int i = 1;i <= n;i++){
scanf("%d",&a[i]);
mp[a[i]] = i;
}
for(int i = 1;i <= n;i++){
scanf("%d",&b[i]);
}
for(int i = 1;i <= n;i++){
int now = get_sum(mp[b[i]]);
modify(mp[b[i]],now+1);
}
printf("%d\n",get_sum(n));
return;
}
int t;
int main(){
t = 1;
while(t--){
solve();
}
return 0;
}
J - 导弹拦截
第一问:最长不上升子序列,树状数组改一下!
第二问:用 set 维护!
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
#define int int
#define DEBUG
#define MAXH 50007
typedef pair<int,int> PII;
const int MAXN = 1e5 + 7;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
array<int,MAXN>c,a,mp;
int n = 0;
int lowbit(int x){
return x & -x;
}
void modify(int x,int val){
while(x > 0){
c[x] = max(c[x],val);
x -= lowbit(x);
}
}
int get_sum(int x){
int res = 0;
while(x <= MAXH){
res = max(res,c[x]);
x += lowbit(x);
}
return res;
}
void solve(){
while(scanf("%d",&a[++n]) != EOF){
mp[a[n]] = n;
}
n--;
int ans = 0;
for(int i = 1;i <= n;i++){
int now = get_sum(a[i]);
// cout << now << endl;
ans = max(ans,now);
modify(a[i],now+1);
}
printf("%d\n",ans + 1);
set<int>st;
for(int i = 1;i <= n;i++){
auto it = st.lower_bound(a[i]);
if(it == st.end()){
st.insert(a[i]);
}else{
st.erase(it);
st.insert(a[i]);
}
}
printf("%d\n",st.size());
return;
}
int t;
int main(){
t = 1;
while(t--){
solve();
}
return 0;
}