折纸

【题目描述】

有一张由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。
    做题的时候要三思而后行,想好每一步,争取用最严谨最巧妙的方法做出来,代码实现能力也是个难点。
*/
posted @ 2016-10-17 21:24  前前前世。  阅读(343)  评论(0编辑  收藏  举报