Codeforces Round #656 (Div. 3) B. Restore the Permutation by Merger
阿新 • • 發佈:2020-07-18
Cloakroom
- 有n件物品,每件物品有三個屬性 a[i],b[i],ci。
- 個詢問,每個詢問由非負整數 m,k,s 組成,問是否能夠選出某些物品使得:
- 對於每個選的物品 i,滿足 a[i]<=m 且 b[i]>m+s。
- 所有選出物品的 c[i]的和正好是 k。
輸入格式
- 第一行一個正整數 n(n<=1,000),接下來 n 行每行三個正整數,分別表示 c[i],a[i],b[i] (c[i]<=1,000,1<=a[i]<b[i]<=109)。
- 下面一行一個正整數 q(q<=1,000,000),接下來 q 行每行三個非負整數 m,k,s(1<=m<=\(10^9\)
輸出格式
- 輸出 q行,每行為 "TAK "(yes)或"NIE"(no),第 i 行對應第 i 次詢問的答案。
樣例輸入
5
6 2 7
5 4 9
1 2 4
2 5 8
1 3 9
5
2 7 1
2 7 2
3 2 0
5 7 2
4 1 5
樣例輸出
TAK
NIE
TAK
TAK
NIE
Solve
看這道題的題解都寫的有些簡單,特此補上一篇稍詳細一些的題解(程式碼有註釋)。
- 題目大意
n件物品分別有屬性a,b,c.
p個詢問分別有屬性m,k,s.
對於每個詢問給出是否存在滿足以下三個條件的情況,有輸出TAK
NIE
。
- 每個物品\(a \leq m\)
- 每個物品\(b>m+s\)
- 所有物品c的和等於k
-
解題思路
-
考慮暴力,列舉k的所有組成情況並進行記錄,每次詢問僅須判斷c的和為k的幾個數,這樣預處理都有\(2^{1000}\),妥妥49分TLE。
-
q[k][i].a
表示和為k的第i種方案中所有物品a屬性的最大值。
q[k][i].b
表示和為k的第i種方案中所有物品b屬性的最小值。
//... struct Node { int a, b; Node() {} Node(int x, int y) { a = x; b = y; } }; //... vector<Node> q[100005];//用q陣列記錄,便於查詢 void Dfs(int x, int big, int small, int sum) { if (big > small || sum > 100000) return; //剪枝優化,a比b大或總和超過資料範圍就不再考慮 q[sum].push_back(Node(big, small)); for (int i = x + 1; i <= n; ++i) Dfs(i, max(big, a[i]), min(small, b[i]), sum + c[i]); } int main() { //... while (Q--) { int m, k, s, f = 0; scanf("%d%d%d", &m, &k, &s); for (int i = 0; i < q[k].size(); ++i)//列舉每種方案進行判斷 if (q[k][i].a <= m && q[k][i].b > m + s) f = 1; puts(f ? "TAK" : "NIE"); } return 0; } //錯誤解法只列出關鍵部分,提供暴力思路
- 這麼大的資料,而且有3個條件需要滿足,考慮離線演算法。
-
看第一個條件:每個物品\(a \leq m\)
將物品按照a的大小排序,詢問按照m的大小排序,這樣,對於第一個條件滿足當前詢問的物品,也一定會滿足後面的詢問的第一個條件,節省了一些時間。 -
每個物品\(b>m+s\),先略過
-
所有物品c的和等於k
類似於揹包問題,需要把揹包裝滿,可以按照揹包的思路進行dp。
-
f[k]
表示,在滿足\(a \leq m\)的物品中c屬性之和為k的方案中最小的 b 屬性的最大值。- 這一點需要重點理解,需要滿足每個物品\(b>m+s\),就需要最小的b比m+s大就可以,但是c屬性的和為k的方案數有可能不止一種,需要找到最優的就是在滿足x是這個方案中最小的b屬性值的前提下儘可能的找x最大的方案。
越說越迷糊,看程式碼可能會好懂一些。
- 這一點需要重點理解,需要滿足每個物品\(b>m+s\),就需要最小的b比m+s大就可以,但是c屬性的和為k的方案數有可能不止一種,需要找到最優的就是在滿足x是這個方案中最小的b屬性值的前提下儘可能的找x最大的方案。
Code
#include <cstdio>
#include <algorithm>
using namespace std;
struct Node1 {
int a, b, c;
bool operator < (const Node1 &b) const {
return a < b.a;
}//過載運算子,對物品按a值從小到大排序
}a[1005];
struct Node2 {
int m, k, s, id;
bool operator < (const Node2 &b) const {
return m < b.m;
}/過載運算子,對詢問按m值從小到大排序
}b[1000005];
int n, q, f[100005];//f陣列題解中的加粗部分進行了詳細的解釋
bool ans[1000005];
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%d%d%d", &a[i].c, &a[i].a, &a[i].b);
scanf("%d", &q);
for (int i = 1; i <= q; ++i)
scanf("%d%d%d", &b[i].m, &b[i].k, &b[i].s), b[i].id = i;
//記錄編號id,離線排序後便於儲存答案
sort(a + 1, a + n + 1);//對物品按a值從小到大排序
sort(b + 1, b + q + 1);//對詢問按m值從小到大排序
f[0] = 1 << 30;//初始化f[0]為極大值,防止在執行 min(f[k-a[j].c], a[j].b)時出現結果都是0的情況
for (int i = 1, j = 1; i <= q; ++i) {
for (; j <= n && a[j].a <= b[i].m; ++j)//滿足條件1:a<=m
for (int k = 100000; k >= a[j].c; --k)
f[k] = max(f[k], min(f[k-a[j].c], a[j].b));
if (f[b[i].k] > b[i].m + b[i].s) ans[b[i].id] = 1;
//滿足條件3:c之和==k
//滿足條件2:b>m+s
}
for (int i = 1; i <= q; ++i)
puts(ans[i] ? "TAK" : "NIE");
//三目運算子,個人比較喜歡使用,挺方便
return 0;
}