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,则认为第一个序列的字典序小于第二个序列。
第一天,每个商店都自己定了一个正整数的价格。店主们希望自己的菜价和其他商店的一致,第二天,每一家商店都会根据他自己和相邻商店的价格调整自己的价格。具体的,每家商店都会将第二天的菜价设置为自己和相邻商店第一天菜价的平均值(用去尾法取整)。
注意,编号为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个正整数,依次表示每个商店第二天的菜价。
输出格式
输出一行,包含n个正整数,依次表示每个商店第一天的菜价。
题解:dfs 记忆化搜索
①递推式:
当i不等于1或n 时,s2[i] = (s1[i-1] + s1[i] + s[i+1]) / 3,所以 3 * s2[i] <= s1[i-1] + s[i] + s[i+1] <= 3 * s2[i] + 2,所以 s1[i+1] = 3*s2[i] - s1[i-1] - s1[i] + 0/1/2,所以可以用s1[i-1]和s1[i]推出s1[i+1]
当i=1时,s2[1] = (s1[1] + s1[2]) / 2, 所以 2 * s2[1] <= s1[1] + s1[2] <= 2 * s2[1] + 1,有两种情况
当i=n时,s2[n]=(s1[n-1]+s1[n]) / 2, 所以 2 * s2[n] <= s1[n-1]+s1[n] <= 2 * s2[n] + 1,有两种情况
②用 f [n] [ s1[n-1] ] [ s1[n] ]来记录是否搜索过该路径,n表示计算到第n个店铺
如果之前已经搜索过,之间返回,否则计算
③最后一个s1[n]要特判,因最后一个s1[n]不仅要满足 s1[n]=2*s2[n]-s1[n-1]+i,i=0/1, 还要满足 s1[n]==3*s2[n-1]-s1[n-1]-s1[n-2] + j, j=0/1/2
也就是说,s1[n]不仅受s2[n]约束,还受s2[n-1]的约束,因此只有同时满足这两个条件才成立
③遇到满足条件的直接打印输出,不要return调回,因为dfs是递归实现的,return会返回上一层dfs,而非主函数,所以会出错。
正确的做法是遇到第一个满足条件的,直接输出,输出完毕之后退出程序,exit(0)
#include <stdio.h> #include <string.h> #include <iostream> #define MAX 305 using namespace std; int n; int s1[MAX]; //第一天的价格 int s2[MAX]; //第二天的价格 int f[MAX][MAX][MAX]; //存储信息 记忆化搜索 f[n][s1[n-1]][s1[n]] //s2[i]=(s1[i-1]+s1[i]+s[i+1])/3,所以 3*s2[i]<= s1[i-1]+s[i]+s[i+1] <=3*s2[i]+2 //s1[i+1]=3*s2[i]-s1[i-1]-s1[i]+0/1/2 //用 s1[i-1]和s1[i] 推出s1[i+1] //s2[1]=(s1[1]+s1[2])/2, 2*s2[1]<= s1[1]+s1[2] <=2*s2[1]+1 //s2[n]=(s1[n-1]+s1[n])/2, 2*s2[n]<= s1[n-1]+s1[n] <=2*s2[n]+1 //s1[n]= 2*s2[n]-s1[n-1] + 0/1 //s1[n]= 3*s2[n-1]-s1[n-2]-s1[n-1] + 0/1/2 void dfs(int m, int x, int y){ //第m个,x为第m-1个的价格,y为第m个的价格 if(f[m][x][y]) return; //已经记录过,直接返回 f[m][x][y]=1; //否则开始计算 if(m==n-1){ //最后一个要特判 for(int i=0;i<2;i++){ s1[n]=2*s2[n]-s1[n-1]+i; //s1[n]要满足s2[n]的条件 if(s1[n]>=1){ for(int j=0;j<3;j++){ if(s1[n]==3*s2[n-1]-s1[n-1]-s1[n-2] + j ){ //s1[n]满足s2[n-1]的条件 for(int j=1;j<=n;j++){ //同时满足两个条件,直接输出 if(j!=n) printf("%d ",s1[j]); else printf("%d\n",s1[j]); } exit(0); //输出之后退出程序 , exit(0)的头文件是 iostream } } } } } for(int i=0;i<3;i++){ s1[m+1]=3*s2[m]-s1[m-1]-s1[m] + i; if(s1[m+1]>=1) //>=1,价格满足条件 dfs(m+1,s1[m],s1[m+1]); } return; } int main(){ while(scanf("%d",&n)!=EOF){ memset(s1,0,sizeof(s1)); memset(s2,0,sizeof(s2)); memset(f,0,sizeof(f)); for(int i=1;i<=n;i++) scanf("%d",&s2[i]); for(int i=1;i<=2*s2[1];i++){ //s1[1]+s1[2]=2*s2[1] || s1[1]+s1[2]= 2*s2[1]+1 s1[1]=i; //s1[1]有两种可能,先试小的那一种 if(i!=2*s2[i]){ s1[2]=2*s2[1]-i; dfs(2,i,s1[2]); } s1[1]=i; //第二种 s1[2]=2*s2[1]+1-i; dfs(2,i,s1[2]); } } return 0; }