CCF CSP 再卖菜(c++)

问题描述

  在一条街上有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.记忆化搜索。

通过这道题加深了对记忆化搜索的理解。递归的弊端就是会产生很多冗余,有些状态可能不止一次的访问。通过标记已访问过哪些状态,当再次访问已访问过的状态时,直接return,可以减少很多冗余。

通过简单的推导,可以发现只要知道第一天前两个商家的菜价,就可以顺着推导出其余商家的菜价。当然,最终要判断下推导出来的最后一个商店的菜价是否正确。

只是加了两行代码,判断是否访问过当前状态,就从超时导致的80分变成了100,不得不感概优化的重要性。

代码如下

#include<iostream>

#include<vector>

#include<string>

#include<map>

#include<set>

#include<queue>

#include<algorithm>

#include<cstdio>

#include<cstring>

#include<sstream>

#define rep(i,start,end) for(int i=start;i<=end;i++)

using namespace std;

typedef long long LL;

typedef pair<LL,LL> pa;

int n;

int v[500];

int a[500];

int res[500];

bool vis[305][305][305];

bool dfs(int k)

{

    //记忆化搜索,将访问过的状态标记,以后碰到相同的状态就可以不妨问了

    if(k==n){

        if((a[n-1]+a[n])/2!=v[n])return false;

        for(int i=1;i<=n;i++)

            cout<<a[i]<<" ";

        return true;

    }

    for(int i=0;i<3;i++){

        a[k+1]=3*v[k]+i-a[k-1]-a[k];

        if(a[k+1]<1){

            continue;

        }

        if(!vis[k][a[k]][a[k+1]]){

            vis[k][a[k]][a[k+1]]=true;

            if(dfs(k+1))return true;

        }

    }

    return false;

}

int main()

{

   // freopen("in.txt","r",stdin);

  //  freopen("out.txt","w",stdout);

    std::ios::sync_with_stdio(false);

    cin>>n;

    for(int i=1;i<=n;i++)cin>>v[i];

    for(a[1]=1;a[1]<2*v[1];a[1]++){

        a[2]=2*v[1]-a[1];

        if(dfs(2))return 0;

        a[2]=2*v[1]+1-a[1];

        if(dfs(2))return 0;

    }

}

2.差分约束

差分约束都转换成xi-xj<=c这种形式,代表j到i有条权重为c的边。

如果求最大值,则跑最短路。如果求最小值,则跑最长路。

因为求的是最小值,所以保证了字典序最小

代码如下

#include<iostream>

#include<vector>

#include<string>

#include<map>

#include<set>

#include<queue>

#include<algorithm>

#include<cstdio>

#include<cstring>

#include<sstream>

#define rep(i,start,end) for(int i=start;i<=end;i++)

using namespace std;

typedef long long LL;

typedef pair<LL,LL> pa;

const int N=305,inf=-1e9;

int n;

int v[N];//a是第一天的价值

bool vis[N];

int dis[N];

struct edge{

    int to,w;

};

vector<edge> G[N];

void solve()

{

    //求最小值,应该求最长路,构造差分约束

    for(int i=1;i<=n;i++){

        //s[i]=s[i-1]+v[i];

        //代表大于等于1,小于等于300的约束

        //G[i-1].push_back({i,0});

        G[i-1].push_back({i,1});

        G[i].push_back({i-1,-300});

    }

    G[0].push_back({2,2*v[1]});

    G[2].push_back({0,-2*v[1]-1});

    G[n-2].push_back({n,2*v[n]});

    G[n].push_back({n-2,-2*v[n]-1});

    for(int i=2;i<n;i++){

        G[i-2].push_back({i+1,3*v[i]});

        G[i+1].push_back({i-2,-3*v[i]-2});

    }

}

void spfa(int s)

{

    queue<int> q;

    q.push(s);

    vis[s]=true;

    for(int i=0;i<=n;i++)dis[i]=inf;

    dis[s]=0;

    while(!q.empty()){

        int u=q.front();q.pop();

        //cout<<"u "<<u<<endl;

        for(auto t:G[u]){

            if(dis[u]+t.w>dis[t.to]){

                dis[t.to]=dis[u]+t.w;

                if(!vis[t.to]){

                    vis[t.to]=true;

                    q.push(t.to);

                }

            }

        }

        vis[u]=false;

    }

}

int main()

{

//    freopen("in.txt","r",stdin);

//    freopen("out.txt","w",stdout);

    std::ios::sync_with_stdio(false);

    cin>>n;

    for(int i=1;i<=n;i++)cin>>v[i];

    solve();

    spfa(0);

    for(int i=1;i<=n;i++)

        cout<<dis[i]-dis[i-1]<<" ";

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值