【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 再卖菜,但是我没有看懂为什么所求得的字典序是最小的

 

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