• 折纸


    【题目描述】

    有一张由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。
        做题的时候要三思而后行,想好每一步,争取用最严谨最巧妙的方法做出来,代码实现能力也是个难点。
    */
  • 相关阅读:
    禁止button响应回车(.net页面)
    windows server 2022避坑指南
    windows中powershell无法使用ng命令
    使用IKVM实现C#调用jar包
    CENTOS7破解ROOT密码
    【原创】LINQ to SQL学习笔记(一)——Where
    【原创】LINQ to SQL学习笔记(四)——Join(1)
    【原创】LINQ to SQL学习笔记(三)——聚集函数(一)
    【原创】LINQ to SQL学习笔记(二)——Select
    cookie 的使用和一些注意事项
  • 原文地址:https://www.cnblogs.com/Ackermann/p/5971339.html
Copyright © 2020-2023  润新知