1. 程式人生 > 實用技巧 >Codeforces Round #656 (Div. 3) B. Restore the Permutation by Merger

Codeforces Round #656 (Div. 3) B. Restore the Permutation by Merger

Cloakroom

題目描述

  • 有n件物品,每件物品有三個屬性 a[i],b[i],ci
  • 個詢問,每個詢問由非負整數 m,k,s 組成,問是否能夠選出某些物品使得:
    1. 對於每個選的物品 i,滿足 a[i]<=m 且 b[i]>m+s。
    2. 所有選出物品的 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\)
    ,1<=k<=100,000,0<=s<=\(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

  1. 每個物品\(a \leq m\)
  2. 每個物品\(b>m+s\)
  3. 所有物品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個條件需要滿足,考慮離線演算法。
  1. 看第一個條件:每個物品\(a \leq m\)
    將物品按照a的大小排序,詢問按照m的大小排序,這樣,對於第一個條件滿足當前詢問的物品,也一定會滿足後面的詢問的第一個條件,節省了一些時間。

  2. 每個物品\(b>m+s\),先略過

  3. 所有物品c的和等於k
    類似於揹包問題,需要把揹包裝滿,可以按照揹包的思路進行dp。

  • f[k]表示,在滿足\(a \leq m\)的物品中c屬性之和為k的方案中最小的 b 屬性的最大值

    • 這一點需要重點理解,需要滿足每個物品\(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;
}