1. 程式人生 > 實用技巧 >[USACO19DEC]Greedy Pie Eaters

[USACO19DEC]Greedy Pie Eaters

題目:Greedy Pie Eaters

網址:https://www.luogu.com.cn/problem/P5851

題目描述

\(Farmer John\)\(M\)頭奶牛,為了方便,編號為\(1,\dots,M\)。這些奶牛平時都吃青草,但是喜歡偶爾換換口味。\(Farmer John\)一天烤了\(N\)個派請奶牛吃,這\(N\)個派編號為\(1,\dots,N\)。第\(i\)頭奶牛喜歡吃編號在\(\left[ l_i,r_i \right]\)中的派(包括兩端),並且沒有兩頭奶牛喜歡吃相同範圍的派。第\(i\)頭奶牛有一個體重\(w_i\),這是一個在\(\left[ 1,10^6 \right]\)

中的正整數。

\(Farmer John\)可以選擇一個奶牛序列 \(c_1,c_2,\dots,c_K\),並讓這些奶牛按這個順序輪流吃派。不幸的是,這些奶牛不知道分享!當奶牛 吃派時,她會把她喜歡吃的派都吃掉——也就是說,她會吃掉編號在 \([l_{c_i},r_{c_i}]\)中所有剩餘的派。\(Farmer John\) 想要避免當輪到一頭奶牛吃派時,她所有喜歡的派在之前都被吃掉了這樣尷尬的情況。因此,他想讓你計算,要使奶牛按 \(c_1,c_2,\dots,c_K\)的順序吃派,輪到這頭奶牛時她喜歡的派至少剩餘一個的情況下,這些奶牛的最大可能體重(\(w_{c_1}+w_{c_2}+\ldots+w_{c_K}\)

)是多少。

輸入格式

第一行包含兩個正整數\(N,M\)

接下來\(M\)行,每行三個正整數 \(w_i,l_i,r_i\)

輸出格式

輸出對於一個合法的序列,最大可能的體重值。

輸入輸出樣例

輸入 #1

2 2
100 1 2
100 1 1

輸出 #1

200

說明/提示

樣例解釋

在這個樣例中,如果奶牛\(1\)先吃,那麼奶牛\(2\)就吃不到派了。然而,先讓奶牛\(2\)吃,然後奶牛\(1\)只吃編號為\(2\)的派,仍可以滿足條件。

對於全部資料,\(1 \le N \le 300,1 \le M \le \dfrac{N(N-1)}{2},1 \le l_i,r_i \le N,1 \le w_i \le 10^6\)

資料範圍

對於測試點\(2-5\),滿足\(N \le 50,M \le 20\)

對於測試點\(6−9\),滿足\(N \le 50\)

USACO 2019 December 鉑金組T1


這是USACO質量極其好得一道。
我們急需解決以下兩個問題:

  • 選取問題,選哪幾個使得權重最大又合法;
  • 順序問題,順序決定是否滿足題意。
    對這兩個問題的理解是否深入將直接導致這道題的做法是否簡潔。

首先,我們考慮:若最終的答案中所選取的奶牛構成一個集合\(S\),則\(S\)中一定有一中排列方式使得每頭牛都能分得“一杯羹“。
這是毋庸置疑的一點。
更近的,如果我們只要找到一種方法判定所選擇的奶牛是否全都有派,那麼我們僅需按照這個方法處理該問題。該問題得以簡化。
事實上,注意到,不妨考慮一種情況:若該奶牛所屬區間為\([l, r]\),則若該區間中存在一個位置k沒有被覆蓋(選擇),那麼該區間就可以被接受。
也就是說,不管順序如何,只要保證至少有一個位置沒有被覆蓋,那麼該奶牛可以被選擇的。

我們把順序問題解決了。

在以上的基礎上,我們考慮使用DP;
\(dp[l,r]\)表示區間\([l,r]\)的派最多能獲得多大的權重和。
顯然有\(dp[l,r]=max(dp[l,k]+dp[k+1,r])\),這表示一個區間是由子區間拼湊出來的。
等等,有些情況我們並沒有包括其中,比如:

注意到,這種情況是因為兩個子區間並沒有“無縫銜接”,中間似乎貼了一段“區間”(這也符合我們剛剛討論過的順序問題),因此,DP方程還應有:\(dp[l,r]=max(dp[l,k-1]+f[l,r,k]+dp[k+1,r])\);
\(f\)指的是區間\([l,r]\)中包含第\(k\)個位置的所有奶牛權重最大的一隻奶牛的權重。預處理即可。
為什麼只預留了一個長度?為什麼對於中間的那一個區間就不能單獨覆蓋更多的位置。這又是一個貪心。前者更優。

C ++ AC程式碼

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
const int SIZE = 50000 + 10;
struct COW
{
	int l, r, w;
} c[SIZE];
int n, m, pos[310][310];
int p[310][310][310], dp[310][310];//dp[i][j] 指的是區間 [i, j] 被吃完的最大體重值
//有方程:  dp[i][j] = max(dp[i][k] + d[k][j], d[i][k - 1] + p[i][j][k], dp[k + 1][j]);
int main()
{
	scanf("%d %d", &n, &m);
	memset(pos, 0, sizeof(pos));
	for(int i = 0; i < m; ++ i)
	{
		scanf("%d %d %d", &c[i].w, &c[i].l, &c[i].r);
		pos[c[i].l][c[i].r] = c[i].w;
	}
	memset(dp, 0, sizeof(dp));
	memset(p, 0, sizeof(p));
	for(int len = 1; len <= n; ++ len)
	{
		for(int i = 1, j = i + len - 1; j <= n; ++ i, ++ j)
		{
			for(int k = i; k <= j; ++ k)
			{
				p[i][j][k] = pos[i][j];
				p[i][j][k] = max(p[i][j][k], max((i < k) * p[i + 1][j][k], (k < j) * p[i][j - 1][k]));
			}
		}
	}
	for(int len = 1; len <= n; ++ len)
	{
		for(int i = 1, j = i + len - 1; j <= n; ++ i, ++ j)
		{
			int &ans = dp[i][j];
			for(int k = i; k <= j; ++ k)
			{
				ans = max(ans, dp[i][k] + dp[k + 1][j]);
				ans = max(ans, dp[i][k - 1] + p[i][j][k] + dp[k + 1][j]);
			}
		}
	}
	printf("%d\n", dp[1][n]);
	return 0;
}

總結回顧

  1. 這道題從看起來好像需要排序滿足題意,到用DP解決完成,發生了題目的轉化。事實上,很多看起來費勁的題目,往往要發覺特性,將其化險為夷。
  2. 另外這道題的兩個轉移值得思索回味。

參考文獻