题目
如果用背包的方式理解:
类似二维费用背包。
f i , j , k f_{i,j,k} fi,j,k 表示前 i i i 个线段, [ 0 , j ] [0,j] [0,j] 花费为 k k k 的最大收益。
f i , e n d i , k = max ( f i − 1 , e n d i , k , f i − 1 , s t a r t i , k − c i + v i ) f_{i,end_i,k}=\max (f_{i-1,end_i,k},f_{i-1,start_i,k-c_i}+v_i) fi,endi,k=max(fi−1,endi,k,fi−1,starti,k−ci+vi)。
省去第一维:
f e n d i , k = max ( f e n d i , k , f s t a r t i , k − c i + v i ) f_{end_i,k}=\max (f_{end_i,k},f_{start_i,k-c_i}+v_i) fendi,k=max(fendi,k,fstarti,k−ci+vi)。
而一般的背包在购买物品时,先买 A A A 再买 B B B、先买 B B B 再买 A A A,两者是等效的,永远是从剩余容量大的转移到剩余容量小的(或者相反)。
而对于此题,要求是从 0 0 0 开始的连续轨道,先造 [ 0 , 2 ] [0,2] [0,2] 再造 [ 2 , 3 ] [2,3] [2,3]、先造 [ 2 , 3 ] [2,3] [2,3] 再造 [ 0 , 2 ] [0,2] [0,2] 是不等效的,会直接导致状态转移的缺失,我们应当将轨道先按右端点排序保证转移的有效性。
但其实我们也不必被背包的 tag 限制住,这本质上也可以理解为一个线性 dp ,是小状态转移到大状态。
#include
#define int long long
using namespace std;
struct Seg{
int x, w, f, c;
}a[10005];
bool cmp(Seg x, Seg y)
{
return (x.x + x.w) < (y.x + y.w);
}
int L, n, B, f[1005][1005];
signed main()
{
ios::sync_with_stdio(false);
std::cin.tie(0);
cin >> L >> n >> B;
for(int i=1; i<=n; i++)
{
cin >> a[i].x >> a[i].w >> a[i].f >> a[i].c;
}
sort(a+1, a+n+1, cmp);
memset(f, 0x80, sizeof(f));
f[0][0] = 0;
for(int i=1; i<=n; i++)
{
for(int k=0; k + a[i].c <= B; k++)
{
f[a[i].x + a[i].w][k + a[i].c] = max(f[a[i].x + a[i].w][k + a[i].c], f[a[i].x][k] + a[i].f);
}
}
int ans = -1;
for(int k=0; k<=B; k++)
ans = max(ans, f[L][k]);
cout << ans;
return 0;
}
题目
n n n 个小时, m m m 头牛,给第 i i i 头牛挤奶需要从 s t i st_i sti 时刻到 e d i ed_i edi 时刻,可以带来收益 c i c_i ci 。每次挤完奶需要休息 r r r 小时( r r r 给定)。
和上题一样为线性 d p dp dp ,下面给出几种 Solution。
f i f_i fi 表示前 i i i 小时,其中第 i i i 小时被覆盖的最优方案。
假设当前枚举线段是第 i i i 个,有:
f r i = max ( f j + c i ) ( 1 ≤ j ≤ l i − r ) f_{r_i}=\max ( f_j +c_i)(1\le j \le l_i-r) fri=max(fj+ci)(1≤j≤li−r) 。
时间复杂度 O ( m n ) O(mn) O(mn)。
signed main()
{
ios::sync_with_stdio(false);
std::cin.tie(0);
cin >> n >> m >> r;
for(int i=1; i<=m; i++)
cin >> a[i].l >> a[i].r >> a[i].c;
sort(a+1, a+m+1, cmp);
int ans = 0;
for(int i=1; i<=m; i++)
{
for(int j=0; j <= max(0LL, a[i].l - r); j++)
{
f[a[i].r] = max(f[a[i].r], f[j] + a[i].c);
}
ans = max(ans, f[a[i].r]);
}
cout << ans;
return 0;
}
对 1 的优化:
将 c i c_i ci 移到 max \max max 外面,有:
f r i = max ( f j ) ( 1 ≤ j ≤ l i − r ) + c i f_{r_i}=\max(f_j)(1\le j \le l_i-r)+c_i fri=max(fj)(1≤j≤li−r)+ci。
用树状数组维护前缀最大值。
时间复杂度 O ( m log n ) O(m \log n) O(mlogn)。
int lowbit(int x) {return x & (-x);}
void modify(int x, int v)
{
for(int i=x; i<=n; i+=lowbit(i))
tree[i] = max(tree[i], v);
}
int query(int x)
{
if(x <= 0) return 0;
int res = 0;
for(int i=x; i>0; i-=lowbit(i))
res = max(res, tree[i]);
return res;
}
signed main()
{
ios::sync_with_stdio(false);
std::cin.tie(0);
cin >> n >> m >> r;
for(int i=1; i<=m; i++)
cin >> a[i].l >> a[i].r >> a[i].c;
sort(a+1, a+m+1, cmp);
int ans = 0;
for(int i=1; i<=m; i++)
{
f[a[i].r] = query(a[i].l - r) + a[i].c;
modify(a[i].r, f[a[i].r]);
ans = max(ans, f[a[i].r]);
}
cout << ans;
return 0;
}
对 1 的优化:
f j f_j fj 的转移只可能是来自之前线段右端点。
将枚举小时变为枚举线段:
f r i = max ( f r j + c i ) ( j < i , 1 ≤ r j ≤ l i − r ) f_{r_i}=\max(f_{r_j}+c_i)(jfri=max(frj+ci)(j<i,1≤rj≤li−r)。
时间复杂度 O ( m 2 ) O(m^2) O(m2)。
signed main()
{
ios::sync_with_stdio(false);
std::cin.tie(0);
cin >> n >> m >> r;
for(int i=1; i<=m; i++)
cin >> a[i].l >> a[i].r >> a[i].c;
sort(a+1, a+m+1, cmp);
int ans = 0;
for(int i=1; i<=m; i++)
{
f[i] = a[i].c;
for(int j=1; a[j].r + r <= a[i].l and j < i; j++)
{
f[i] = max(f[i], f[j] + a[i].c);
}
ans = max(ans, f[i]);
}
cout << ans;
return 0;
}