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 结尾时我们最多能抓多少鼹鼠。

所以转移就是

\[f[i] = \max(f[i],f[j] + 1) \]

好像 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;
}

后记:mp 一点用都没有!!!

后后记:后面两题暂时不会

后后后记:动态规划得多刷题!!!

总结:动态规划可太难了!!!

posted @ 2025-09-06 18:07  Ruochen_xia  阅读(18)  评论(0)    收藏  举报