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的正整数。
  请注意,以上都是给的第二天菜价的范围,第一天菜价可能会超过此范围。

题解1

这与第一题不同的是已知第二天的卖菜价格反过来求第一天的卖菜价格,有很多解。这里要求是字典序最小。最容易想到的方法就是DFS+记忆化搜索。参照博客。如果第二天的价格记为 b 1 , b 2 , b 3 … b n b_1, b_2,b_3\dots b_n b1,b2,b3bn,然后第一天的价格记为 a 1 , a 2 , a 3 … a n a_1, a_2,a_3\dots a_n a1,a2,a3an
第一天价格与第二天价格存在如下关系
b 1 = ( a 1 + a 2 ) / 2 b 2 = ( a 1 + a 2 + a 3 ) / 3 b 3 = ( a 2 + a 3 + a 4 ) / 3 … b n − 1 = ( a n − 2 + a n − 1 + a n ) / 3 b n = ( a n − 1 + a n ) / 2 b_1 = (a_1 + a_2) / 2 \\ b_2 = (a_1 + a_2 + a_3) / 3 \\ b_3 = (a_2 + a_3 + a_4) / 3 \\ \dots \\ b_{n-1} = (a_{n - 2} + a_{n - 1} + a_{n}) / 3 \\ b_{n} = (a_{n - 1} + a_{n}) / 2 b1=(a1+a2)/2b2=(a1+a2+a3)/3b3=(a2+a3+a4)/3bn1=(an2+an1+an)/3bn=(an1+an)/2
除了 a n a_n an b n b_n bn的关系, a i a_i ai b i b_i bi都为正整数即
a i ≥ 1 b i ≥ 1 a_i \geq 1 \\ b_i \geq 1 ai1bi1
单纯只有DFS不剪枝可以很轻松通过剪30%的测试用例。
b 1 = ( a 1 + a 2 ) / 2 b_1 = (a_1 + a_2) / 2 b1=(a1+a2)/2可以得到 2 ∗ b 1 ≤ a 1 + a 2 ≤ 2 ∗ b 1 + 1 2*b_1 \leq a_1 + a_2 \leq 2 * b_1 + 1 2b1a1+a22b1+1所以a_1的值的范围为 1 − 2 ∗ b 1 + 1 1-2 * b_1+1 12b1+1,而 a 2 a_2 a2的值为 2 ∗ b 1 − a 1 2*b_1 - a_1 2b1a1或者 2 ∗ b 1 + 1 − a 1 2*b_1 + 1 - a_1 2b1+1a1。对于1 - n 的递推关系为 a i = 3 ∗ b i − 1 − a i − 1 − a i − 2 a_i = 3 * b_{i - 1} - a_{i - 1} - a_{i - 2} ai=3bi1ai1ai2 或者 a i = 3 ∗ b i − 1 + 1 − a i − 1 − a i − 2 a_i = 3 * b_{i - 1} + 1- a_{i - 1} - a_{i - 2} ai=3bi1+1ai1ai2或者 a i = 3 ∗ b i − 1 + 2 − a i − 1 − a i − 2 a_i = 3 * b_{i - 1} + 2 - a_{i - 1} - a_{i - 2} ai=3bi1+2ai1ai2,大大限制搜索范围,对于 a n a_n an有2个限制条件 a n = 3 ∗ b n − 1 + i − a n − 1 − a n − 2 ( i = 0 , 1 , 2 ) a_n= 3 * b_{n - 1} + i - a_{n- 1} - a_{n - 2} (i = 0, 1, 2) an=3bn1+ian1an2(i=0,1,2) a n = 2 ∗ b n + i − a n − 1 ( i = 0 , 1 ) a_n = 2*b_n + i - a_{n - 1}(i =0, 1) an=2bn+ian1(i=0,1),这个需要注意。这样搜索可以通过80%的测试用例,后20%的测试用例会超时。这里需要加入记忆化搜索,一个三维标记数组,来标记搜索过的情况,避免走重复的路。浪费时间,具体见代码。

代码1

//
// Created by Onwaier Lee on 2019-11-16.
// DFS
//
#include <bits/stdc++.h>
using namespace std;

#define MAX 305
int a[MAX], b[MAX];
int n;
bool flag = false;
bool f[MAX][MAX][MAX];//储存状态信息
void dfs(int idx, int num1, int num2){
    if(flag || f[idx][num1][num2]){//剪枝 不然只有80分
        return;
    }
    f[idx][num1][num2] = 1;
    if(idx == n){
        for(int i = 0; i < 3; ++i){
            a[n] = 3 * b[idx - 1] + i - num1 - num2;
            if(a[n] > 0 && (a[n] + num2) / 2 == b[n]){
                flag = true;
                for(int i = 1; i <= n; ++i){
                    printf("%s%d", i == 1?"":" ", a[i]);
                }
                printf("\n");
                exit(0);
            }
        }
        return;
    }
    else if(idx > n){//只有2个数
        if((num1 + num2) / 2 == b[n]){
            printf("%d %d\n", a[1], a[2]);
            flag = true;
            exit(0);
        }
    }
    for(int i = 0; i < 3; ++i){
        a[idx] = 3 * b[idx - 1] + i - num1 - num2;
        if(a[idx] > 0)
            dfs(idx + 1, num2, a[idx]);
    }
}

int main(){
    //freopen("//Users//onwaier//CLionProjects//CCFSolutions//a.txt", "r", stdin);
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i){
        scanf("%d", &b[i]);
    }
    //a[1]的范围是1 - 2 * b[1] - 1
    for(int i = 1; i <= 2 * b[1] + 1; ++i){
        a[1] = i;
        //a[2]的第一种情况
        a[2] = 2 * b[1] - a[1];
        if(!flag && a[2] > 0)
            dfs(3, a[1], a[2]);
        //a[2]的第二种情况
        a[2] = 2 * b[1] - a[1] + 1;
        if(!flag && a[2] > 0)
            dfs(3, a[1], a[2]);
    }

    return 0;
}

题解2

参照博客,觉得自己无论如何也想不到这种解法。一是对差分约束不熟悉,另外建立差分约束比较有技巧性,三是字典序最小,求最长路径而不是最短路径(用的是SPFA)。具体参照给的博客链接。

代码2

//
// Created by Onwaier Lee on 2019-11-16.
//  差分约束 最短路(其实是最长路)
//
#include <bits/stdc++.h>
using namespace std;
#define MAX 305

struct Node{
    int v;
    int w;
    Node(int v, int w):v(v), w(w){}
};
int a[MAX], b[MAX], dist[MAX], cnt[MAX];//dist[i] = a[0] + a[1] + …… + a[i]
bool mark[MAX];
vector<vector<Node>>graph(MAX);


bool spfa(int sta, int n){//用于求最长路
    //初始化
    memset(dist, 0, sizeof(dist));
    memset(mark, false, sizeof(mark));
    memset(cnt, 0, sizeof(cnt));//计数 处理负环

    queue<int>que;
    int frt;
    que.push(0);
    ++cnt[0];
    mark[0] = true;
    while (!que.empty()){
        frt = que.front();
        que.pop();
        mark[frt] = false;
        for(int i = 0; i < graph[frt].size(); ++i){
            int u = frt, v = graph[frt][i].v, w = graph[frt][i].w;
            if(dist[v] < dist[u] + w){//松弛条件与最短路时不同
                dist[v] = dist[u] + w;
                if(!mark[v]){
                    mark[v] = true;
                    ++cnt[v];
                    que.push(v);
                    if(cnt[v] > n + 1){
                        return false;
                    }
                }
            }
        }
    }
    return true;
}
int main(){
    //freopen("//Users//onwaier//CLionProjects//CCFSolutions//a.txt", "r", stdin);
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i){
        scanf("%d", &b[i]);
    }
    for(int i = 0; i < n - 2; ++i){//除了第一个和最后一个
        graph[i].push_back(Node(i + 3, 3 * b[i + 2]));
        graph[i + 3].push_back(Node(i, -3 * b[i + 2] - 2));
    }
    //特殊处理第一个与最后一个
    graph[0].push_back(Node(2, 2 * b[1]));
    graph[2].push_back(Node(0, -2 * b[1] - 1));
    graph[n - 2].push_back(Node(n, 2 * b[n]));
    graph[n].push_back(Node(n - 2, -2 * b[n] - 1));

    //每一个都要不小于1
    for(int i = 0; i < n; ++i){
        graph[i].push_back(Node(i + 1, 1));
    }

    spfa(0, n);
    for(int i = 1; i <= n; ++i){
        a[i] = dist[i] - dist[i - 1];
    }
    for(int i = 1; i <= n; ++i){
        printf("%s%d", i == 0?"":" ", a[i]);
    }
    return 0;
}

github地址

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值