• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 众包
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

yumiym765

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

一些dp题目

一些常见的线性 \(dp\),处理方式类似

C. Boring Day

\(\hspace{20px}\)Egor 有一副 \(n\) 的扑克牌,最上面的 \(i\) 张牌上写着数字 \(a_i\) 。Egor 想玩一定轮数的游戏,直到牌用完为止。在每一轮中,他都会从牌面顶端抽取一张非零数的牌,然后结束这一轮。如果在这一轮中收集到的纸牌上的数字之和在 \(l\) 和 \(r\) 之间(包括首尾数字),则这一轮获胜;否则,这一轮失败。

\(\hspace{20px}\)Egor 对纸牌的顺序了如指掌。请帮助 Egor 确定他在这样的游戏中最多可以赢多少局。请注意,Egor 不需要连续获胜。

\(\hspace{20px}\)\(1 \le n \le 10^5,1 \le l \le r \le 10^9,1 \le a_i \le 10^9\)。

\(\hspace{20px}\)Link

动态规划

选或不选,如果选,就跳到下一个 \(sum\) 在 \(l\) 和 \(r\) 之间的位置。

对于 \(sum\) 的处理,我们可以用前缀和,对于每个位置,我们用二分查找下一个满足条件的最小位置,如果该位置存在,就跳过去寻找。

正向 dp
点击查看代码
void solve(){
	int n, L, R;
	cin >> n >> L >> R;
	vector<long long> a(n + 1), pre(n + 1, 0), dp(n + 1);
	dp[0] = 0;
	for(int i = 1;i <= n;i++){
		cin >> a[i];
		pre[i] = pre[i - 1] + a[i];
	}
	for(int i = 1;i <= n;i++){
		int l = 1, r = i, tmp = -1;
		while(l <= r){
			int mid = (l + r) >> 1;
			long long sum = pre[i] - pre[mid - 1];
			if(L <= sum && sum <= R){
				tmp = mid;
				l = mid + 1;
			}else if(sum <= L){
				r = mid - 1;
			}else{
				l = mid + 1;
			}
		}
		dp[i] = dp[i - 1];
		if(tmp != -1){
			dp[i] = max(dp[i], dp[tmp - 1] + 1);
		}
	}
	cout << dp[n] << "\n";
}
反向 dp
点击查看代码
void solve(){
	int n, L, R;
	cin >> n >> L >> R;
	vector<long long> a(n + 1), pre(n + 1, 0), dp(n + 2);
	dp[n + 1] = 0;
	for(int i = 1;i <= n;i++){
		cin >> a[i];
		pre[i] = pre[i - 1] + a[i];
	}
	for(int i = n;i >= 1;i--){
		int l = i, r = n, tmp = -1;
		while(l <= r){
			int mid = (l + r) >> 1;
			long long sum = pre[mid] - pre[i - 1];
			if(L <= sum && sum <= R){
				tmp = mid;
				r = mid - 1;
			}else if(sum <= L){
				l = mid + 1;
			}else{
				r = mid - 1;
			}
		}
		dp[i] = dp[i + 1];
		if(tmp != -1){
			dp[i] = max(dp[i], dp[tmp + 1] + 1);
		}
	}
	cout << dp[1] << "\n";
}
双指针 + 贪心

---------------------- 分割线 ----------------------

D. Kousuke's Assignment

\(\hspace{20px}\)老师给了他一个由 \(n\) 个整数组成的数组 \(a\) ,要求他计算数组 \(a\) 中不重叠的段的数量,这样每一段都被认为是美丽的。

\(\hspace{20px}\)如果有 \(a_l + a_{l+1} + \dots + a_{r-1} + a_r=0\) 段 \([l,r]\) 被认为是美丽的。

\(\hspace{20px}\)对于一个固定的数组 \(a\) ,你的任务是计算不重叠的美丽线段的最大数目。

\(\hspace{20px}\)\(1 \le n \le 10^5,-10^5 \le a_i \le 10^5\)。

\(\hspace{20px}\)Link

动态规划

选或不选,如果选,就跳到从以该点开始的美丽线段的结尾。

对于美丽线段的处理,我们可以用前缀和,对于每个位置,如果前缀和 \(pre_l = pre_r\),有从 \(l \dots r - 1\) 或 \(l + 1 \dots r\) 位置的和为 \(0\),我们预处理时记录下一个满足条件的最小位置 \(lst\),如果该位置存在,就跳过去寻找。

对于数组只有 \(0\) 元素的处理:提前预处理 \(pos_0\) 为 \(0\)。

Fun fact : 注意别爆 int 了,题解给的代码爆了=)。

点击查看代码
void solve(){
    int n;
    cin >> n;
    vector<long long> a(n + 1), pre(n + 1, 0);
    vector<int> lst(n + 1, -1);
    map<long long, int> pos;
    pos[0] = 0;
    for(int i = 1;i <= n;i++){
        cin >> a[i];
        pre[i] = pre[i - 1] + a[i];
        if(pos.find(pre[i]) != pos.end()){
            lst[i] = pos[pre[i]];
        }
        pos[pre[i]] = i;
    }
    vector<int> dp(n + 1);
    dp[0] = 0;
    for(int i = 1;i <= n;i++){
        dp[i] = dp[i - 1];
        if(lst[i] != -1){
            dp[i] = max(dp[i], 1 + dp[lst[i]]);
        }
    }
    cout << dp[n] << "\n";
}

---------------------- 分割线 ----------------------

C. Against the Difference

\(\hspace{20px}\)我们定义块是指数组中所有元素的长度都等于数组长度的数组。例如, \([3, 3, 3]\) 、 \([1]\) 和 \([4, 4, 4, 4]\) 是数块,而 \([1, 1, 1]\) 和 \([2, 3, 3]\) 不是数块。

\(\hspace{20px}\)如果一个数组可以由任意数量的数块(可能为零)连接而成,那么这个数组就被称为整齐数组。请注意,空数组总是整齐的。

\(\hspace{20px}\)给你一个由 \(n\) 个整数组成的数组 \(a\) 。求其最长整齐子序列的长度。

\(\hspace{20px}\)\(1 \le n \le 10^5,1 \le a_i \le n\)。

\(\hspace{20px}\)Link

动态规划

选或不选,如果选,就跳到从以该点开始的块的结尾。

对于块的处理,观察到 \(a_i\) 的范围,我们可以用二维数组(类似邻接表)记录,预处理时,每个数字都把下标 存进去,顺便存个个数 \(cnt\)。更新时,\(cnt\) 也一起更新,如果个数足够,就跳过去更新 \(dp\) 答案。

正向 dp
点击查看代码
void solve() {
    int n;
    cin >> n;
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
        a[i]--;
    }
    
    vector<vector<int>> vec(n);
    vector<int> id(n);
    for (int i = 0; i < n; i++) {
        id[i] = vec[a[i]].size();
        vec[a[i]].push_back(i);
    }
    
    vector<int> dp(n + 1);
    for (int i = 0; i < n; i++) {
        dp[i + 1] = dp[i];
        if (id[i] >= a[i]) {
            dp[i + 1] = max(dp[i + 1], a[i] + 1 + dp[vec[a[i]][id[i] - a[i]]]);
        }
    }
    cout << dp[n] << "\n";
}
反向 dp
点击查看代码
void solve(){
	int n;
	cin >> n;
	vector<int> a(n);
	vector<int> dp(n + 1, INT_MAX);
	vector<int> cnt(n + 1, 0);
	vector<vector<int>> pos(n + 1);
	for(int i = 0;i < n;i++){
		cin >> a[i];
		cnt[a[i]]++;
		pos[a[i]].push_back(i);
	}
	dp[n] = 0;
    for(int i = n - 1;i >= 0;i--){
    	cnt[a[i]]--;
    	dp[i] = dp[i + 1];
    	if(cnt[a[i]] + a[i] <= pos[a[i]].size()){
    		dp[i] = max(dp[i], dp[pos[a[i]][cnt[a[i]]+a[i]-1] + 1] + a[i]);
		}
	}
	cout << dp[0] << "\n";
}

posted on 2025-09-08 11:21  羊毛corn  阅读(15)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3