【CCF 201809-4】再買菜(記憶化搜索)

題意

問題描述
  在一條街上有n個賣菜的商店,按1至n的順序排成一排,這些商店都賣一種蔬菜。
  第一天,每個商店都自己定了一個正整數的價格。店主們希望自己的菜價和其他商店的一致,第二天,每一家商店都會根據他自己和相鄰商店的價格調整自己的價格。具體的,每家商店都會將第二天的菜價設置爲自己和相鄰商店第一天菜價的平均值(用去尾法取整)。
  注意,編號爲1的商店只有一個相鄰的商店2,編號爲n的商店只有一個相鄰的商店n-1,其他編號爲i的商店有兩個相鄰的商店i-1和i+1。
  給定第二天各個商店的菜價,可能存在不同的符合要求的第一天的菜價,請找到符合要求的第一天菜價中字典序最小的一種。
  字典序大小的定義:對於兩個不同的價格序列(a1, a2, …, an)和(b1, b2, b3, …, bn),若存在i (i>=1), 使得ai<bi,且對於所有j<i,aj=bj,則認爲第一個序列的字典序小於第二個序列。
輸入格式
  輸入的第一行包含一個整數n,表示商店的數量。
  第二行包含n個正整數,依次表示每個商店第二天的菜價。
輸出格式
  輸出一行,包含n個正整數,依次表示每個商店第一天的菜價。
樣例輸入
8
2 2 1 3 4 9 10 13
樣例輸出
2 2 2 1 6 5 16 10
數據規模和約定
  對於30%的評測用例,2<=n<=5,第二天每個商店的菜價爲不超過10的正整數;
  對於60%的評測用例,2<=n<=20,第二天每個商店的菜價爲不超過100的正整數;
  對於所有評測用例,2<=n<=300,第二天每個商店的菜價爲不超過100的正整數。
  請注意,以上都是給的第二天菜價的範圍,第一天菜價可能會超過此範圍。

記憶化搜索  

對於本題的20%用例,使用普通的深度優先搜索時不能在規定時間內完成的。因此我們考慮剪枝:

由於某個商家的菜價只取決於前兩個商家的菜價,因此我們可以知道:如果兩個連續的商家的價格確定下來,那麼在後續的搜索中剩餘的商家的價格是確定的。

因此我們定義一個visit[x][cur][pre]:表示"第x個商家的菜價爲cur,第x-1個商家的菜價爲pre"的情況已經遍歷過了,繼續遍歷這種情況肯定是不能夠得到正確解的,因此我們直接把它剪掉。即記憶化剪枝。

滿分代碼(C++)

#include <iostream>
using namespace std;

int n;
int first[301];     // 第一天的菜價
int second[301];    // 第二天的菜價
bool visit[301][201][301];  // visit[x][cur][pre]:第x個商家的菜價爲cur,第x-1個商家的菜價爲pre的情況已經遍歷過了

/* 第d個賣家取值爲val */
bool dfs(int d, int val)
{
    // 記憶化搜索
    if (visit[d][val][first[d - 1]]) return false;   // 剪枝:若搜過,則說明這個分支不行,直接剪掉
    visit[d][val][first[d - 1]] = true;   // 記憶:記錄這種情況已經被搜索過了

    if (val <= 0) return false;     // val的合法性
    first[d] = val;

    // first[1] = val時,根據second[1]求出first[2]的可能取值
    if (d == 1)
    {
        int temp = second[1] * 2;
        // 注意||的短路性質:左邊的爲true時右邊將不會進行遞歸
        return dfs(2, temp - first[1]) || dfs(2, temp - first[1] + 1);
    }
    // d ∈ [2, n]時
    else if (d <= n)
    {
        int temp = second[d] * 3;
        return dfs(d + 1, temp - first[d - 1] - first[d]) ||
               dfs(d + 1, temp - first[d - 1] - first[d] + 1) ||
               dfs(d + 1, temp - first[d - 1] - first[d] + 2);
    }
    // 當完成第n個商家的賦值時,需要檢查second[n]是否匹配
    else return (first[n - 1] + first[n]) / 2 == second[n];
}

int main()
{
    // 關閉同步
    ios::sync_with_stdio(false);

    // 輸入
    cin >> n;
    for (int i = 1; i <= n; ++i)
        cin >> second[i];

    // 考察第一個商家的可能取值爲[1, 2*second[1]]
    for (int i = 1; i <= 2 * second[1]; ++i)
    {
        bool flag = dfs(1, i);
        if (flag == true) break;
    }

    // 輸出結果
    cout << first[1];
    for (int i = 2; i <= n; ++i)
        cout << ' ' << first[i];
    return 0;
}

差分約束系統(圖論做法)

知識鋪墊:差分約束與最短路徑

【參考dalao】ccf 201809-4 再賣菜,但是我沒有看懂爲什麼所求得的字典序是最小的

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章