算法入门经典第六章 例题6-5 移动盒子

例题 6-5 移动盒子(Boxes in a Line, UVa127675)

问题 给定一行盒子,从左到右编号依次为1,2,...,n.可以执行以下命令:

1 X Y 把盒子 X 移动到 Y 的左边(如果已经在左边,忽略此命令)

2 X Y 把盒子 X 移动到 Y 右边(如果X已经在Y的右边,忽略此命令)

3 X Y交换 X 和 Y 的位置

4 把整个顺序颠倒

指令保证合法,即X 不等于 Y, 输入包含不超过10组数据,每组第一行为盒子的数目n和指令的数目m(1<=n,m<=100000)

Output  :For each test case, print the sum of numbers at odd-indexed positions. Positions are numbered 1 to nfrom left to right.

样例输入:

6 4
1 1 4
2 3 5
3 1 6
4
6 3
1 1 4
2 3 5
3 1 6
100000 1
4

样例输出:

Case 1: 12
Case 2: 9
Case 3: 2500050000

如果用数组来求解,复杂度太高,每次要移动大量元素,因此很容易想到用双向链表求解。 当然可以用 自己构造双向链表,或者使用STL的list。受到前一题的启发,其实可用两个 int 数组来构造双向链表,right 数组表示当前元素的下一个元素的下标,left表示前一个元素的下标。

i 0 1 2 3 4
left[i] 4 1 2 3 0
right[i] 1 3 4 2 0
若交换 2和3   1 2 3 4-> 1 3 2 4 (right[i])  变成 

i代表元素位置下标 sum+=1+2=3

i 0 1 2 3 4
left[i] 4 0 3 1 2
right[i] 1 3 4 2 0
 

分析:

用到了双向链表。

1。用了两个数组left[maxn],right[maxn]代表当前元素的左边一个或者右边一个,当这个值为0的时候代表不存在!

2。对于4号命令,逆转整个序列,并没有真正的逆转,而是用inv 记录 是否逆转,利用了逆转两次等于没有逆转这个道理。逆转只会影响到1命令和2命令,3命令是XY换一下,并不会影响到,所以对与1和2,直接op = 3 - op即可!利用inv这个变量也有利于最后的输出,最后输出发现inv是0的话,就是没逆转,那么直接把奇数位置的数加起来即可,反之,要用总和减去这个偶数序列,(因为当n是偶数并且逆转的情况,sum其实是偶数位置!)

3。最后注意的一点,对于3号命令,是XY置换,XY相邻和XY相隔很多元素,处理是不一样的。

如果用数组来求解,复杂度太高,每次要移动大量元素,因此很容易想到用双向链表求解。 当然可以用 自己构造双向链表,或者使用STL的list。受到前一题的启发,其实可用两个 int 数组来构造双向链表,rightt数组表示当前元素的下一个元素的下标,leftt表示前一个元素的下标。

#include<iostream>
#include<cstdio>
using namespace std;
const int maxn = 100000 + 10;
int rightt[maxn],leftt[maxn];////left and righttt is ambiguous
void link(int x,int y)
{
    rightt[x]=y;
    leftt[y]=x;
}
int main()
{
    int n,m,cnt=0;
    while(scanf("%d%d",&n,&m) == 2)
    {
        for (int i = 0; i <= n; ++i)
        {
            leftt[i]=i-1;
            rightt[i]=(i+1)%(n+1);
        }
        leftt[0]=n;
        rightt[n]=0;
        int op,inv=0,X,Y;
        while(m--)
        {
            scanf("%d",&op);
            if (op == 4)inv = !inv;  //偶数列 项转置会变成奇数项 1 2 3 4 -> 4 3 2 1
            else
            {
                scanf("%d%d",&X,&Y);
                if(op==3&&rightt[Y]==X) //为了转化为同一种情况来处理,x、y只是一个代号,反正结果变得是值
                    swap(X,Y);

                if (inv && op != 3)op = 3 - op;
                if (op == 1 && rightt[X] == Y)continue;
                if (op == 2 && rightt[Y] == X)continue;
                int LX=leftt[X],RX=rightt[X],RY=rightt[Y],LY=leftt[Y];
                if (op == 1)
                {
                    link(LX,RX);
                    link(LY,X);
                    link(X,Y);
                }
                if (op == 2)
                {
                    link(LX,RX);
                    link(Y,X);
                    link(X,RY);
                }
//执行命令 3时候,注意如果X和Y是相邻的,需要特殊处理;
                if (op == 3)
                {
                    if(rightt[X]==Y)
                    {
                        link(LX,Y);
                        link(Y,X);
                        link(X,RY);
                    }
                    else
                    {
                        link(LX,Y);
                        link(Y,RX);
                        link(LY,X);
                        link(X,RY);
                    }

                }
            }
        }
        //0 1 2 3 4 ->1 3 2 4 0 交换2和3 结果 1+2
        long long ans=0;
        int b = 0;
        for (int i = 1; i <= n; ++i)
        {
            b = rightt[b];  //向右推移
            if (i % 2 == 1)ans+=b;
        }
//
//如果n不是偶数的话,倒一下结果也一样
//最后输出发现inv是0的话,就是没逆转,那么直接把奇数位置的数加起来即可,反之,要用总和减去这个偶数序列,(因为当n是偶数并且逆转的情况,

//sum其实是偶数位置

        if (n % 2 == 0 && inv)ans = (long long)(n+1)*n/2-ans;
        printf("Case %d: %lld\n",++cnt,ans);
    }
    return 0;
}

用left right定义数组会有歧义

left and right are already defined in namespace std, which you are importing all of with using namespace std. That‘s why you have an ambiguity.

法二:在op==3 分下来三种情况 XY相邻两种 不相邻 1种

#include<iostream>
#include<cstdio>
using namespace std;
const int maxn = 100000 + 10;
int rightt[maxn],leftt[maxn];
void link(int x,int y)
{
    rightt[x]=y;
    leftt[y]=x;
}
int main()
{
    int n,m,cnt=0;
    while(scanf("%d%d",&n,&m) == 2)
    {
        for (int i = 0; i <= n; ++i)
        {
            leftt[i]=i-1;
            rightt[i]=(i+1)%(n+1);
        }
        leftt[0]=n;
        rightt[n]=0;
        int op,inv=0,X,Y;
        while(m--)
        {
            scanf("%d",&op);
            if (op == 4)inv = !inv;
            else
            {
                scanf("%d%d",&X,&Y);
                if (inv && op != 3)op = 3 - op;
                if (op == 1 && rightt[X] == Y)continue;
                if (op == 2 && rightt[Y] == X)continue;
                int LX=leftt[X],RX=rightt[X],RY=rightt[Y],LY=leftt[Y];
                if (op == 1)
                {
                    link(LX,RX);
                    link(LY,X);
                    link(X,Y);
                }
                if (op == 2)
                {
                    link(LX,RX);
                    link(Y,X);
                    link(X,RY);
                }
//执行命令 3时候,注意如果X和Y是相邻的,需要特殊处理;
                if (op == 3)
                {
                    if(rightt[X]==Y)
                    {
                        link(LX,Y);
                        link(Y,X);
                        link(X,RY);
                    }
                    else if(rightt[Y]==X)
                    {
                        link(LY,X);
                        link(X,Y);
                        link(Y,RX);

                    }
                    else{
                        link(LX,Y);
                        link(Y,RX);
                        link(LY,X);
                        link(X,RY);
                    }

                }
            }
        }
        //0 1 2 3 4 ->1 3 2 4 0 交换2和3 结果 1+2
        long long ans=0;
        int b = 0;
        for (int i = 1; i <= n; ++i)
        {
            b = rightt[b];  //向右推移
            if (i % 2 == 1)ans+=b;
        }
//如果n不是偶数的话,倒一下结果也一样
//最后输出发现inv是0的话,就是没逆转,那么直接把奇数位置的数加起来即可,反之,要用总和减去这个偶数序列,(因为当n是偶数并且逆转的情况,

//sum其实是偶数位置
//偶数列 项转置会变成奇数项 1 2 3 4 -> 4 3 2 1
        if (n % 2 == 0 && inv)ans = (long long)(n+1)*n/2-ans;
        printf("Case %d: %lld\n",++cnt,ans);
    }
    return 0;
}
时间: 2024-10-12 04:44:19

算法入门经典第六章 例题6-5 移动盒子的相关文章

算法入门经典第六章 例题6-14 Abbott的复仇(Abbott&#39;s Revenge)BFS算法实现

Sample Input 3 1 N 3 3 1 1 WL NR * 1 2 WLF NR ER * 1 3 NL ER * 2 1 SL WR NF * 2 2 SL WF ELF * 2 3 SFR EL * 0 Sample Output (3,1) (2,1) (1,1) (1,2) (2,2) (2,3) (1,3) (1,2) (1,1) (2,1) (2,2) (1,2) (1,3) (2,3) (3,3) 析 题目的大意是,输入起点,离开起点时的朝向和终点,求一条最短路. 每一个

算法入门经典第六章 例题6-15 给任务排序

假设有n个变量,还有m个二元组(u,v),分别表示变量u小于v.那么,所有变量从小到大排列起来应该是什么样子呢?例如,有4个变量a,b,c,d,若已知a<b,c<b,d<c,则这4个变量的排序可能是a<d<c<b.尽管还有其他可能(如d<a<c<b),你只需找出其中一个即可. Sample Input 5 4 1 2 2 3 1 3 1 5 0 0 Sample Output 1 4 2 5 3 题意:假设有n个变量,还有m个二元组(u,v),分别表示

算法入门经典-第七章 例题7-4-1 拓展 n皇后问题 回溯法

实际上回溯法有暴力破解的意思在里面,解决一个问题,一路走到底,路无法通,返回寻找另   一条路. 回溯法可以解决很多的问题,如:N皇后问题和迷宫问题. 一.概念 回溯算法实际类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现不满足条件的时候,就回溯返回,尝试别的路径. 百度解释:回溯法(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标.但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯

算法入门经典-第七章 例题7-2 八皇后问题

原本利用回溯思想解决的经典八皇后问题,其实也是可以用递归解决的~ 八皇后的递归解决思路: 从第一行开始,依次判断0~8列的哪一列可以放置Queen,这样就确定了该行的Queen的位置,然后行数递增,继而递归实现下一行的判断,依次类推直到行数增加到8(行数从0开始的),此时为递归-----归的条件,即表示一种八皇后的解决方法完成,打印结果:之后进行下一种解决方法的寻找,大致思路个人理解是这样 noDanger(row,j,(*chess)[8])函数是判断第row行第j列是否可以放置Queen #

算法入门经典-第七章 例题7-2-2 可重集的排列

可重:如果问题变成输入数组p,并按字典序输出数组A个元素的所有全排列,则需要修改代码集的全排列 // Rujia Liu #include<cstdio> #include<algorithm> using namespace std; int P[100], A[100]; // 输出数组P中元素的全排列.数组P中可能有重复元素 void print_permutation(int n, int* P, int* A, int cur) { if(cur == n) { for(

算法入门经典-第五章 例题6-10 下落的树叶

给一颗二叉树,每个节点都有一个水平位置:左子结点在它的左边一个单位,右子节点在右 边一个单位.从左向右输出每个水平位置的所有节点的权值之和.如图所示,从左到右的三 个位置权和分别为 7,11,3,按照递归(先序)方式输入,用-1 表示空树. Sample Input  5 7 -1 6 -1 -1 3 -1 -1  8 2 9 -1 -1 6 5 -1 -1 12 -1 -1 3 7 -1 -1 -1 -1  Sample Output  Case 1: 7 11 3  Case 2: 9 7

算法入门经典-第七章 例题7-1 除法

除法输入正整数n,按从小到大的顺序输出所有形如abcde/fghij=n的表达式,其中a~j恰好为数字0~9的一个排列,2<=n<=79. 样例输入: 62 样例输出: 79546/01238=62 94736/01528=62 #include<stdio.h> #include<string.h> int main() { int n; //x/y=n x用abcde表示,若整除n,求出y,而后用fghij表示Y,看是否重复数字 while(~scanf("

算法入门经典-第五章 例题 5-5 集合栈计算机

The SetStack Computer Time limit: 3.000 seconds 题目是这样的: 有一个专门为了集合运算而设计的"集合栈"计算机.该机器有一个初始为空的栈,并且支持以下操作:PUSH:空集"{}"入栈DUP:把当前栈顶元素复制一份后再入栈UNION:出栈两个集合,然后把两者的并集入栈INTERSECT:出栈两个集合,然后把二者的交集入栈ADD:出栈两个集合,然后把先出栈的集合加入到后出栈的集合中,把结果入栈       每次操作后,输出

算法入门经典-第四章 例题4-3 救济金发放

救济金的问题抽象出来就是几个人围成一个圈坐,给每一个人编号,一个人从1开始,一个人从n开始,从一开始的点到k时,出列一人,n逆时针点人,点到m出列一人.如果我们出列用删除操作,则大大的降低了效率,我们将删除掉的人用0来代替,当我们遇到0时不点人. 使用两个方法来分别逆时针顺时针点人,如果是0,则跳过 n(n<20)个人站成一圈,逆时针编号为1-n.有两个官员,A从1开始逆时针数,B从n开始顺时针数.在每一轮中,官员A数k个就停下来,官员B数m个就停下来(注意有可能两个官员停在同一个人上).接下来