算法竞赛入门经典(第二版) 刘汝佳-第八章 高效算法设计 例题(14/19)

说明

本文是我对第八章19道例题的练习总结,建议配合紫书——《算法竞赛入门经典(第2版)》阅读本文。
另外为了方便做题,我在VOJ上开了一个contest,欢迎一起在上面做:第八章例题contest
如果想直接看某道题,请点开目录后点开相应的题目!!!

例题

例8-1 UVA 120 煎饼

题意
有一叠煎饼正在锅里。煎饼共有n(n≤30)张,每张都有一个数字,代表它的大小。厨师每次可以选择一个数k,把从锅底开始数第k张上面的煎饼全部翻过来,即原来在上面的煎饼现在到了下面。
设计一种方法使得所有煎饼按照从小到大排序(最上面的煎饼最小)。输入时,各个煎饼按照从上到下的顺序给出。

思路
这道题目要求排序,但是基本操作却是“颠倒一个连续子序列”。不过没有关系,我们还是可以按照选择排序的思想,以从大到小的顺序依次把每个数排到正确的位置。方法是先翻到最上面,然后翻到正确的位置。由于是按照从大到小的顺序处理,当处理第i大的煎饼时,是不会影响到第1, 2, 3,…, i-1大的煎饼的(它们已经正确地翻到了煎饼堆底部的i-1个位置上)。

开始马虎了,将if (j > 1)判断条件不慎写成了if (j < 1)提交后WA了两发,提醒自己一定要细心啊!

代码

#include<cstdio>
#include<cstring>
#include<string>
#include<iostream>
#include<sstream>
#include<algorithm>
#include<vector>
using namespace std;

int n;
int a[31];

void flip(int k)
{
    for (int i = 1; i <= k/2; i++)
      swap(a[i], a[k-i+1]);
}

int main()
{
    string s;
    //freopen("in", "r", stdin);
    while (getline(cin, s)) {
        stringstream ss(s);
        int x;
        n = 0;
        while (ss >> x)
          a[++n] = x;

        int b[31];
        memcpy(b, a, sizeof(a));
        sort(b+1, b+n+1);

        vector<int> res;
        for (int i = n; i >= 1; i--) {
            if (a[i] != b[i]) {
                int j = 1;
                for (; a[j] != b[i]; j++);
                if (j > 1) {flip(j); res.push_back(n-j+1);}
                flip(i); res.push_back(n-i+1);
            }
        }

        cout << s << endl;
        for (int i = 0; i < res.size(); i++)
          printf("%d ", res[i]);
        printf("0\n");
    }

    return 0;
}

例8-2 UVA 1605 联合国大楼

题意
你的任务是设计一个包含若干层的联合国大楼,其中每层都是一个等大的网格。有若干国家需要在联合国大楼里办公,你需要把每个格子分配给一个国家,使得任意两个不同的国家都有一对相邻的格子(要么是同层中有公共边的格子,要么是相邻层的同一个格子)。你设计的大厦最多不能超过1000000个格子。
输入国家的个数n(n≤50),输出大楼的层数H、每层楼的行数W和列数L,然后是每层楼的平面图。不同国家用不同的大小写字母表示。

思路
本题的限制非常少,层数、行数和列数都可以任选。正因为如此,本题的解法非常多。我采用的是书中给出的解法:一共只有两层,每层都是n*n的,第一层第i行全是国家i,第二层第j列全是国家j。

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

char ans[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

int main()
{
    int n;
    while (scanf("%d", &n) != EOF){
        printf("2 %d %d\n", n, n);
        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; j++)
              printf("%c", ans[i]);
            printf("\n");
        }
        printf("\n");
        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; j++)
              printf("%c", ans[j]);
            printf("\n");
        }
    }
    return 0;
}

例8-3 UVA 1152 和为 0 的 4 个值

题意
给定4个n(1≤n≤4000)元素集合A, B, C, D,要求分别从中选取一个元素a, b, c, d,使得
a+b+c+d=0。问:有多少种选法?

思路
中途相遇法。这是一种特殊的算法,大体思路是从两个不同的方向来解决问题,最终“汇集”到一起。

最容易想到的算法就是写一个四重循环枚举a, b, c, d,看看加起来是否等于0,时间复杂度为O(n4),超时。一个稍好的方法是枚举a, b, c,则只需要在集合D里找找是否有元素-a-bc,如果存在,则方案加1。如果排序后使用二分查找,时间复杂度为(n3logn)。
把刚才的方法加以推广,就可以得到一个更快的算法:首先枚举a和b,把所有a+b记录下来放在一个有序数组或者STL的map里,然后枚举c和d,查一查-c-d有多少种方法写成a+b的形式。两个步骤都是O(n2logn),总时间复杂度也是O(n2logn)。

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int N = 4000;

int n, m;
int a[4][N];
int x[N*N], y[N*N];

int main(void)
{
    int kase;
    cin >> kase;
    while (kase--) {
        cin >> n;
        m = n*n;
        for (int i = 0; i < n; i ++)
          for (int j = 0; j < 4; j ++)
            scanf("%d", &a[j][i]);
        for (int i = 0; i < n; i ++)
          for (int j = 0; j < n; j ++)
            x[i*n+j] = a[0][i] + a[1][j];
        for (int i = 0; i < n; i ++)
          for (int j = 0; j < n; j ++)
            y[i*n+j] = a[2][i] + a[3][j];
        sort(y, y+m);

        long long ans = 0;
        for (int i = 0; i < m; i ++)
          ans += (upper_bound(y, y+m, -x[i]) - lower_bound(y, y+m, -x[i]));
        printf("%lld\n", ans);
        if (kase) printf("\n");
    }

    return 0;
}

例8-4 UVA 11134 传说中的车

题意
你的任务是在n*n的棋盘上放n(n≤5000)个车,使得任意两个车不相互攻击,且第i个车在一个给定的矩形Ri之内。用4个整数xli, yli, xri, yri(1≤xli≤xri≤n,1≤yli≤yri≤n)描述第i个矩形,其中(xli,yli)是左上角坐标,(xri,yri)是右下角坐标,则第i个车的位置(x,y)必须满足xli≤x≤xri,yli≤y≤yri。如果无解,输出IMPOSSIBLE;否则输出n行,依次为第1,2,…,n个车的坐标。

思路
两个车相互攻击的条件是处于同一行或者同一列,因此不相互攻击的条件就是不在同一行,也不在同一列。可以看出:行和列是无关的,

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值