【题目描述】
有一张由N个格子组成的小纸条,每个格子上面有一个整数,每两个相邻格子之间有一条分界线。
可以选择任意一条分界线,将整张纸条向左进行翻折,如果翻折后有两个格子重叠,那么翻折后格子的上数字为原两个格子的数字之和,否则其数字保持不变。也可以将整张纸条向左进行翻转,翻转之后第i个格子的数字变成第(N-i+1)个格子的数字。
现需要将纸条变成一张包含M个格子,每个格子的数字为Mi的理想纸条,询问能否通过上述操作使纸条变成理想纸条。
【输入描述】
输入最多五组数据,每组数据格式如下:
第一行输入一个整数N,表示初始纸条的格子数目;
第二行输入N个整数,表示每个初始格子的数字;
第三行输入一个整数M(M <= N),表示理想纸条的格子数目;
第四行输入M个整数,表示每个理想格子的数字。
【输出描述】
对于每组数据输出一行,每行包含一个字母,“S”表示可行,“N”表示不可行。
【样例输入】
7
5 6 23 8 19 7 10
4
5 16 30 27
7
1 2 3 4 5 6 7
5
7 6 5 5 5
4
1 2 3 4
1
10
6
19 23 3 51 2 0
2
34 64
【样例输出】
S
S
S
N
【数据范围及提示】
对于70%数据,N <= 10;
对于100%数据,N <= 15。
XZC神犇的上帝之解:
源代码: #include<cstdio> #include<algorithm> using namespace std; int m,n,Num(0),Start[16],End[16],Vis[16],i[16]; bool Check(int T) //感觉如此神奇的检验函数。 { Num++; //Num表示情况代号,即用来判断格子是否已被找过。 int Step=1,Left=15,Right=0,Sign=8; //Left、Right表示左右边界,Sign表示当前点,赋中间值是为了防止越界。 for (int a=0;a<n;a++) { if ((T>>a)&1) //从右边出栈,并且默认右折。 Step=-Step; else Sign+=Step; Left=min(Sign,Left); Right=max(Sign,Right); //不断地更新边界。 if (Vis[Sign]!=Num) //从未找到就初始化。 { i[Sign]=0; Vis[Sign]=Num; } i[Sign]+=Start[a]; //加和。 } if (Right-Left+1!=m) //进行比对。 return false; for (int a=Left;a<=Right;a++) if (End[a-Left]!=i[a]) return false; return true; } int main() { while (scanf("%d",&n)==1) { bool Flag(0); for (int a=0;a<n;a++) scanf("%d",&Start[a]); scanf("%d",&m); for (int a=0;a<m;a++) scanf("%d",&End[a]); for (int a=0;a<1<<n;a++) if (Check(a)) //枚举情况的十进制数。 { Flag=true; break; } if (Flag) puts("S"); else puts("N"); } return 0; } /* 拿个纸条折一折就会发现,指定某几条折痕,无论怎么折,折出来的都是同一样的结果。 如同样例1: 5 6 23 8 19 7 10 包括最左边的折痕,共有7条折痕,也就是共有2^7种答案。 用二进制表示(倒序排列),1表示折,0表示不折,例如: 0 1 0 0 1 1 0 折出来应是: (5+6+23) (8+10) (7+19) 即: 34 18 26 那么进行暴力枚举就可以了,比DFS更快,当然也更难理解。 讲讲这个神奇的函数过程: 首先Step=1,格子就会右向拓展,Step=-1,格子就会左向拓展。 而且如果后面折痕都表示为0,则会一直拓展下去。 如果折痕表示为1,那么就会取相反数,往后就会反向拓展,并且会发现,这样可以处理多出来的格子的重合问题。 默认折痕在当前点右侧,所以现处理现在的,更新的Step留到下一个循环即可。 */
就这数据范围蛤蛤蛤!来个暴力!
源代码: #include<cstdio> #include<cstring> #include<algorithm> using namespace std; int n,m,End[16],i[16][16]; bool DFS(int Deep,int Left,int Right) //Deep表示深搜层数,即翻折次数,Left、Right分别表示纸条的左右边界。 { int Length=Right-Left+1; if (Length<m) //长度不符合要求。 return false; if (Length==m) //检验数字是否符合要求。 { int Flag(0); for (int a=Left,b=1;a<=Right;a++,b++) //正面比较。 if (i[Deep][a]!=End[b]) { Flag=true; break; } if (!Flag) return true; Flag=false; for (int a=Left,b=m;a<=Right;a++,b--) //反面比较。 if (i[Deep][a]!=End[b]) Flag=true; if (!Flag) return true; return false; } for (int a=Right;a>=Right-(Length>>1)+1;a--) //枚举的是编号!中间线右边的折痕,也就是说折过去,左边可能会出现空余。 { int b,c; for (b=a-1,c=a;c<=Right;b--,c++) //范围要变一变。 i[Deep+1][b]=i[Deep][b]+i[Deep][c]; for (;b>=Left;b--) //要灵活变换方向。 i[Deep+1][b]=i[Deep][b]; if (DFS(Deep+1,Left,a-1)) return true; } for (int a=Left;a<Left+(Length-1>>1);a++) //同理于上,左边折痕。 { int b,c; for (b=a+1,c=a;c>=Left;b++,c--) i[Deep+1][b]=i[Deep][b]+i[Deep][c]; for (;b<=Right;b++) i[Deep+1][b]=i[Deep][b]; if (DFS(Deep+1,a+1,Right)) return true; } return false; } int main() //还以为多么烧脑,就他妈一个普通的深搜啊,不过想不出方法来确实也束手无策。 { freopen("fold.in","r",stdin); freopen("fold.out","w",stdout); while (scanf("%d",&n)==1) { int Sum1(0),Sum2(0); for (int a=1;a<=n;a++) { scanf("%d",&i[1][a]); Sum1+=i[1][a]; //初始数字和。 } scanf("%d",&m); for (int a=1;a<=m;a++) { scanf("%d",&End[a]); Sum2+=End[a]; //最总数字和。 } if (Sum1!=Sum2) //预判。 { puts("N"); continue; } if (m==1) //特判。 { puts("S"); continue; } DFS(1,1,n)?puts("S"):puts("N"); } return 0; fclose(stdin); fclose(stdout); } /* 枚举折痕来进行DFS。 做题的时候要三思而后行,想好每一步,争取用最严谨最巧妙的方法做出来,代码实现能力也是个难点。 */