[USACO06DEC]Cow Roller Coaster S & [USACO07NOV]Milking Time S 题解 线段覆盖类题目

T1

题目

如果用背包的方式理解:

类似二维费用背包。

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(fi1,endi,k,fi1,starti,kci+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,kci+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;
}

T2

题目

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。

1

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)(1jlir)

时间复杂度 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;
}


2

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)(1jlir)+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;
}

3

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,1rjlir)

时间复杂度 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;
}



你可能感兴趣的:(题解,c++,算法,动态规划)