title: DP优化
tags: [DP, DP优化]
categories: [OI, 算法专题]

ABC415G Get Many Cola

原题 · 标签:#DP #DP优化

考点

分析性质题

题意

  • 初始有 \(N\) 瓶可乐,空瓶数为 \(0\)。可重复执行以下操作任意次:
    1. 喝掉 \(1\) 瓶可乐(需有瓶装可乐),瓶装可乐 \(-1\),空瓶 \(+1\)。
    2. 选择一种兑换方案 \(i\)(\(1 \le i \le M\)),用 \(A_i\) 个空瓶换 \(B_i\) 瓶可乐(需空瓶足够)。
  • 求最多能喝到的可乐总数。

数据范围:\(1 \le N \le 10^{15}\),\(1 \le M \le 2 \times 10^5\),\(1 \le B_i < A_i \le 300\)。

思路

首先考虑DP式子。DP[i] = j表示有\(i\)瓶可乐实际上能喝\(j\)瓶。当\(i\)大于等于\(a_i\)时,将行为看做物品,体积为\(a_i - b_i\),价值为\(a_i\)。
发现\(a_i\),\(b_i\)很小。考虑我们可以一直选择性价比最高的方案直到>=\((max a_i) ^ 2\)。因为在这之前我们肯定可以选到杂碎的方案。
所以实际上预处理DP[i]到\(90000\)就可以了。

代码

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
#define f(i,a,b) for(int i=(a);i<=(b);i++)
#define Dl(a) cout << #a << " : " << a << "\n";
#define D(a) cout << #a << " : " << a;
#define Da(a,i,j) cout << #a << " : ";f(idx,i,j){cout << a[idx] <<" ";}
const int N = 3e2+10;
int wu[N];
int F[N*N];
int n,m;
signed main(){
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	cin >> n >> m;
	f(i,1,m){
		int a,b;cin >> a >> b;
		wu[a] = max(b,wu[a]);
	}
	f(i,1,90000){
		F[i] = i;
		f(j,1,300){
			if (i >= j){
				F[i] = max(F[i],F[i - j + wu[j]]  + j);
			}
		}
	}
//	Da(wu,1,5);
//	Da(F,1,5);
	if (n <= 90000){
		cout << F[n];
		return 0;
	}
	double mx = -10000;
	int idx = 0;
	f(i,1,300){
		int V = i - wu[i];
		int W = wu[i];
		if (!W) continue;
		if (mx < W * 1.0 / V){
			mx = W * 1.0 / V;
			idx = i;
		}
	}
//	Dl(idx);
	int V = idx - wu[idx];
	int W = idx;
	int u = (n - 90000) / V + 1;
	int ans = F[n-u*V] + u * W;
	cout << ans <<"\n";
	return 0;
}

P1545 USACO04DEC Dividing the Path G

原题 · 标签:#DP #DP优化 #单调队列

考点

单调队列优化+差分处理限制

题意

  • 在 \([0, L]\) 的数轴上放置喷灌器,每个喷灌器位于整数坐标,射程 \(x\) 为整数且 \(A \le x \le B\),灌溉范围为 \([i-x, i+x]\),必须在 \([0, L]\) 内。
  • 要求整个 \([0, L]\) 被完全覆盖,且喷灌器的灌溉区域不重叠。
  • 有 \(N\) 个奶牛喜爱的草区 \([S_i, E_i]\),每个草区必须被恰好一个喷灌器完整覆盖。
  • 求最少需要的喷灌器数量,若无解输出 \(-1\)。

数据范围:\(1 \le L \le 10^6\)(\(L\) 为偶数),\(1 \le A \le B \le 10^3\),\(1 \le N \le 10^3\)。

思路

发现最优解一定有使得所有放喷灌的点是偶数的情况,所以只在偶数位DP就可以。
不重叠的问题,用一个ban数组维护。发现开区间内必不能放喷灌。
发现区间内要维护i-2b ~ i-2a的dp最小值。想到单调队列。

代码

#include <bits/stdc++.h>
using namespace std;
//#define int long long
#define pii pair<int,int>
#define f(i,a,b) for(int i=(a);i<=(b);i++)
#define Dl(a) cout << #a << " : " << a << "\n";
#define D(a) cout << #a << " : " << a;
#define Da(a,i,j) cout << #a << " : ";f(idx,i,j){cout << a[idx] <<" ";}
const int N = 1e6+10;
int n,l,a,b;
int d[N],ban[N];
int dp[N];
deque<int> q; 
signed main(){
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	cin >> n >> l >> a >> b;
	f(i,1,n){
		int s,e; cin >>s >> e;
		d[s+1] ++;d[e] --;
	}
	f(i,1,l){
		ban[i] = ban[i-1] + d[i];
	}
	memset(dp,0x3f,sizeof dp);
	dp[0] = 0;
	for (int i=2;i<=l;i+=2){
		// 维护单调队列 
		int lef = max(0,i-2*b),rig = max(0,i-2*a);
		// 弹出过期队首 
		while (!q.empty() && q.front() < lef){
			q.pop_front();
		}
		int j = rig;
		if (!ban[j] && i-2*a >= 0){
			while (!q.empty() && dp[q.back()] >= dp[j]){
				q.pop_back();
			}
			q.push_back(j);
		}
		if (ban[i]) continue;
//		D(i);Dl(ban[i]);
		if (!q.empty()) dp[i] = dp[q.front()] + 1;
		
//		D(i);Dl(dp[i]);
	}
	if (dp[l] >= 0x3f3f3f3f) cout << -1;
	else cout << dp[l];
	return 0;
}

CF1854B Earn or Unlock

原题 · 标签:#DP

考点

bitset优化

题意

  • 有 \(n\) 张牌从上到下排列,第 \(i\) 张数值为 \(a_i\)。初始只有第一张牌未锁定,其余锁定。
  • 每回合选择一张未锁定牌(数值为 \(v\)),执行以下操作之一后将其移除:
    1. 解锁从顶部开始的前 \(v\) 张锁定牌(不足则全解锁)。
    2. 获得 \(v\) 点胜利分数。
  • 当没有未锁定牌或牌堆为空时游戏结束。求最大胜利分数。

数据范围:\(1 \le n \le 10^5\),\(0 \le a_i \le n\)。

思路

很容易想到,分数是总数-用于解锁的分数。
考虑在当前位解锁实际上是将当前可行性左移 \(v\) 位,用bitset解决正好。
需要注意处理到i时,dp i要最后设为0。因为其对应到 i 位结束。

P11233 CSP-S 2024 染色

原题 · 标签:#DP #DP优化

考点

分析性质设计DP