《算法竞赛进阶指南》0.8总结与练习

Posted wmxnlfd

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《算法竞赛进阶指南》0.8总结与练习相关的知识,希望对你有一定的参考价值。

116. 飞行员兄弟

“飞行员兄弟”这个游戏,需要玩家顺利的打开一个拥有16个把手的冰箱。

已知每个把手可以处于以下两种状态之一:打开或关闭。

只有当所有把手都打开时,冰箱才会打开。

把手可以表示为一个4х4的矩阵,您可以改变任何一个位置[i,j]上把手的状态。

但是,这也会使得第i行和第j列上的所有把手的状态也随着改变。

请你求出打开冰箱所需的切换把手的次数最小值是多少。

输入格式
输入一共包含四行,每行包含四个把手的初始状态。
符号“+”表示把手处于闭合状态,而符号“-”表示把手处于打开状态。
至少一个手柄的初始状态是关闭的。

输出格式
第一行输出一个整数N,表示所需的最小切换把手次数。
接下来N行描述切换顺序,每行输入两个整数,代表被切换状态的把手的行号和列号,数字之间用空格隔开。

数据范围
1≤i,j≤4

输入样例:

-+--
----
----
-+--

输出样例:
6
1 1
1 3
1 4
4 1
4 3
4 4

注意:如果存在多种打开冰箱的方式,则按照优先级整体从上到下,同行从左到右打开

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

typedef pair<int, int> PII;
int change[4][4];

int get(int x, int y)
{
    return x * 4 + y;
}

int main()
{
    int state = 0;
    for(int i = 0; i < 4; i++) //用一个整数state表示4*4 的矩阵
    {
        string line;
        cin >> line;
        for(int j = 0; j < 4; j++)
            if(line[j] == '+')
                state += 1 << get(i, j); // get()函数改变状态
    }
    for(int i = 0; i < 4; i++) //操作某行某列的一个数转换为异或一个数的操作
        for(int j = 0; j < 4; j++)
        {
            for(int k = 0; k < 4; k++)
            {
                change[i][j] += 1 << get(i, k); //改变行
                change[i][j] += 1 << get(k, j); //改变列
            }
            change[i][j] -= 1 << get(i, j); // 加了两次,减去一次
        }
    vector<PII>res;
    for(int k = 0; k < 1 << 16; k++) //枚举所有情况一共2的16次方不同状态
    {
        int now = state;
        vector<PII>path;
        for(int i = 0; i < 16; i++)
            if(k >> i & 1) //k 的第 i位 是1 判断操作哪个格子,是1就操作
            {
                int x = i / 4, y = i % 4; //第i个格子的编号 x,y坐标
                now ^= change[x][y];
                path.push_back({x, y});
            }
            
        if(!now && ( res.empty() || res.size() > path.size()) ) res = path;    
    }
    cout << res.size() << endl;
    for(auto p : res) cout << p.first + 1 << ' ' << p.second + 1 << endl;
    return 0;
}

117. 占卜DIY

达达学会了使用扑克DIY占卜。
方法如下:
一副去掉大小王的扑克共52张,打乱后均分为13堆,编号1~13,每堆4张,其中第13堆称作“生命牌”,也就是说你有4条命。
这里边,4张K被称作死神。
初始状态下,所有的牌背面朝上扣下。
流程如下:
1.抽取生命牌中的最上面一张(第一张)。
2.把这张牌翻开,正面朝上,放到牌上的数字所对应编号的堆的最上边。(例如抽到2,正面朝上放到第2堆牌最上面,又比如抽到J,放到第11堆牌最上边,注意是正面朝上放)
3.从刚放了牌的那一堆最底下(最后一张)抽取一张牌,重复第2步。(例如你上次抽了2,放到了第二堆顶部,现在抽第二堆最后一张发现是8,又放到第8堆顶部.........)
4.在抽牌过程中如果抽到K,则称死了一条命,就扔掉K再从第1步开始。
5.当发现四条命都死了以后,统计现在每堆牌上边正面朝上的牌的数目,只要同一数字的牌出现4张正面朝上的牌(比如4个A),则称“开了一对”,当然4个K是不算的。
6.统计一共开了多少对,开了0对称作”极凶”,1-2对为“大凶”,3对为“凶”,4-5对为“小凶”,6对为“中庸”,7-8对“小吉”,9对为“吉”,10-11为“大吉”,12为“满堂开花,极吉”。

输入格式
一共输入13行数据,每行四个数字或字母,表示每堆牌的具体牌型(不区分花色只区分数字),每堆输入的顺序为从上到下。
为了便于读入,用0代表10。
同行数字用空格隔开。

输出格式
输出一个整数,代表统计得到的开出的总对数。

输入样例:
8 5 A A
K 5 3 2
9 6 0 6
3 4 3 4
3 4 4 5
5 6 7 6
8 7 7 7
9 9 8 8
9 0 0 0
K J J J
Q A Q K
J Q 2 2
A K Q 2

输出样例:
9

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 14;

vector<int> cards[14];
int open[14];

int get(char c) //求每个符号表示的数字 
{
    if( c == 'A') return 1;
    if( c >= '2' && c <= '9') return c - '0';
    if(c == '0') return 10;
    if(c == 'J') return 11;
    if(c == 'Q') return 12;
    return 13;
}

int main()
{
    for(int i = 1; i <= 13; i++)
    {
        for(int j = 0; j < 4; j++)
        {
            char s[2]; //长度为2的字符串来读取字符 自动过滤回车制表符
            cin >> s;
            cards[i].push_back(get(*s)); //*s 等价于 s[0] 取第一个元素
        }
    }
    
    for(int i = 0; i < 4; i++)
    {
        int t = cards[13][i];
        while(t != 13)
        {
            open[t] ++;
            int r = cards[t].back(); // 返回最后一个元素
            cards[t].pop_back();
            t = r;
        }
    }
    
    int res = 0;
    for(int i = 1; i <= 12; i ++) res += open[i] >= 4;
    cout << res <<endl;
    return 0;
}

118. 分形

分形,具有以非整数维形式充填空间的形态特征。
通常被定义为“一个粗糙或零碎的几何形状,可以分成数个部分,且每一部分都(至少近似地)是整体缩小后的形状”,即具有自相似的性质。
现在,定义“盒子分形”如下:
一级盒子分形:

X

二级盒子分形:

   X X
    X
   X X

如果用B(n - 1)代表第n-1级盒子分形,那么第n级盒子分形即为:

  B(n - 1)        B(n - 1)

          B(n - 1)

  B(n - 1)        B(n - 1)

你的任务是绘制一个n级的盒子分形。

输入格式
输入包含几个测试用例。
输入的每一行包含一个不大于7的正整数n,代表要输出的盒子分形的等级。
输入的最后一行为-1,代表输入结束。

输出格式
对于每个测试用例,使用“X”符号输出对应等级的盒子分形。
请注意’X’是一个大写字母。
每个测试用例后输出一个独立一行的短划线。

输入样例:
1
2
3
4
-1

输出样例

X
-
X X
 X
X X
-
X X   X X
 X     X
X X   X X
   X X
    X
   X X
X X   X X
 X     X
X X   X X
-
X X   X X         X X   X X
 X     X           X     X
X X   X X         X X   X X
   X X               X X
    X                 X
   X X               X X
X X   X X         X X   X X
 X     X           X     X
X X   X X         X X   X X
         X X   X X
          X     X
         X X   X X
            X X
             X
            X X
         X X   X X
          X     X
         X X   X X
X X   X X         X X   X X
 X     X           X     X
X X   X X         X X   X X
   X X               X X
    X                 X
   X X               X X
X X   X X         X X   X X
 X     X           X     X
X X   X X         X X   X X
-
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;

char g[N][N];

void dfs(int n)
{
    if(n == 1)
    {
        g[0][0] = 'X';
        return;
    }
    
    dfs(n - 1);
    
    int len = 1;
    for(int i = 0; i < n-2; i++) len *=3; //求n-1级图形的边长 3^n-2
    
    int sx[4] = {0, 1, 2, 2}, sy[4] = {2, 1, 0, 2};
    for(int k = 0; k < 4; k ++)
        for(int i = 0; i < len; i++)
            for(int j = 0; j < len; j++)
                g[sx[k] * len + i][sy[k] * len + j] = g[i][j]; //复制
}

int main()
{
    dfs(7);
    
    int n;
    while(cin >> n, n != -1)
    {
        int len = 1;
        for(int i = 0; i < n - 1; i ++) len *= 3; //一级是1,二级是3,三级是9,n级是3的n-1次方
        for(int i = 0; i < len; i++)
        {
            for(int j = 0; j < len; j ++)
                if(g[i][j]) cout << 'X';
                else cout << ' ';
            cout << endl;    
        }
        cout << '-' << endl; //短划线
    }
    return 0;
}

119. 袭击

在与联盟的战斗中屡战屡败后,帝国撤退到了最后一个据点。
依靠其强大的防御系统,帝国击退了联盟的六波猛烈进攻。
经过几天的苦思冥想,联盟将军亚瑟终于注意到帝国防御系统唯一的弱点就是能源供应。
该系统由N个核电站供应能源,其中任何一个被摧毁都会使防御系统失效。
将军派出了N个特工进入据点之中,打算对能源站展开一次突袭。
不幸的是,由于受到了帝国空军的袭击,他们未能降落在预期位置。
作为一名经验丰富的将军,亚瑟很快意识到他需要重新安排突袭计划。
他现在最想知道的事情就是哪个特工距离其中任意一个发电站的距离最短。
你能帮他算出来这最短的距离是多少吗?

输入格式
输入中包含多组测试用例。
第一行输入整数T,代表测试用例的数量。
对于每个测试用例,第一行输入整数N。
接下来N行,每行输入两个整数X和Y,代表每个核电站的位置的X,Y坐标。
在接下来N行,每行输入两个整数X和Y,代表每名特工的位置的X,Y坐标。

输出格式
每个测试用例,输出一个最短距离值,结果保留三位小数。
每个输出结果占一行。

数据范围
1≤N≤100000,
0≤X,Y≤1000000000

输入样例:
2
4
0 0
0 1
1 0
1 1
2 2
2 3
3 2
3 3
4
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0

输出样例:
1.414
0.000

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 200010;
const double INF = 1e10;

struct Point
{
    double x, y;
    bool type;
    bool operator< (const Point &W) const
    {
        return x < W.x;
    }
}points[N], temp[N];

double dist(Point a, Point b)
{
    if(a.type == b.type) return INF;
    double dx = a.x - b.x, dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
}

double dfs(int l, int r)
{
    if(l >= r) return INF;//当前区间最多只有一个元素
    int mid = l + r >> 1;
    double mid_x = points[mid].x; //分界线
    double res = min(dfs(l, mid), dfs(mid + 1, r)); // 递归左边和右边
    //变量作用域,限制在括号里面
    {
        //归并排序
        int k = 0, i = l, j = mid + 1; //i = L
        while(i <= mid && j <= r)
            if(points[i].y < points[j].y) temp[k++] = points[i++];
            else temp[k++] = points[j++];
        while(i <= mid) temp[k++] = points[i++];
        while(j <= r) temp[k++] = points[j++];
        
        for(i = 0, j = l; i < k; i++, j++) points[j] = temp[i]; //把临时数据放回去 j从 L开始循环    
    }
    
    int k = 0;
    for(int i = l; i <= r; i++) //i = L
        if(points[i].x >= mid_x - res && points[i].x <= mid_x + res)
            temp[k++] = points[i];
            
    for(int i = 0; i <= k; i++) 
        for(int j = i - 1; j >=0 && temp[i].y - temp[j].y <= res; j --)
            res = min(res, dist(temp[i], temp[j]));
            
    return res;        
}

int main()
{
    int T, n;
    cin >> T;
    while(T --)
    {
        cin >> n;
        for(int i = 0; i < n; i++)
        {
            cin >> points[i].x >> points[i].y;
            points[i].type = 0;
        }
        for(int i = n; i < n * 2; i++) //这里 i 由 n 到 n * 2
        {
            cin >> points[i].x >> points[i].y;
            points[i].type = 1;
        }
        
        sort(points, points + n * 2);
        printf("%.3f\n", dfs(0, n * 2 - 1));
    }
    return 0;
}

120. 防线

达达学习数学竞赛的时候受尽了同仁们的鄙视,终于有一天......受尽屈辱的达达黑化成为了黑暗英雄怪兽达达。
就如同中二漫画的情节一样,怪兽达达打算毁掉这个世界。
数学竞赛界的精英 lqr 打算阻止怪兽达达的阴谋,于是她集合了一支由数学竞赛选手组成的超级行动队。
由于队员们个个都智商超群,很快,行动队便来到了怪兽达达的黑暗城堡的下方。
但是,同样强大的怪兽达达在城堡周围布置了一条“不可越过”的坚固防线。
防线由很多防具组成,这些防具分成了 N 组。
我们可以认为防线是一维的,那么每一组防具都分布在防线的某一段上,并且同一组防具是等距离排列的。

也就是说,我们可以用三个整数 S, E 和 D 来描述一组防具,即这一组防具布置在防线的 S,S + D,S + 2D,…,S + KD(K∈ Z,S + KD≤E,S + (K + 1)D>E)位置上。

黑化的怪兽达达设计的防线极其精良。如果防线的某个位置有偶数个防具,那么这个位置就是毫无破绽的(包括这个位置一个防具也没有的情况,因为 0 也是偶数)。

只有有奇数个防具的位置有破绽,但是整条防线上也最多只有一个位置有奇数个防具。

作为行动队的队长,lqr 要找到防线的破绽以策划下一步的行动。

但是,由于防具的数量太多,她实在是不能看出哪里有破绽。作为 lqr 可以信任的学弟学妹们,你们要帮助她解决这个问题。

输入格式
输入文件的第一行是一个整数 T,表示有 T 组互相独立的测试数据。
每组数据的第一行是一个整数 N。
之后 N 行,每行三个整数 Si,Ei,Di,代表第 i 组防具的三个参数,数据用空格隔开。

输出格式
对于每组测试数据,如果防线没有破绽,即所有的位置都有偶数个防具,输出一行 "There‘s no weakness."(不包含引号) 。
否则在一行内输出两个空格分隔的整数 P 和 C,表示在位置 P 有 C 个防具。当然 C 应该是一个奇数。

数据范围
防具总数不多于10^8,
Si≤Ei,
1≤T≤5,
N≤200000,
0≤Si,Ei,Di≤231?1

输入样例:
3
2
1 10 1
2 10 1
2
1 10 1
1 10 1
4
1 10 1
4 4 1
1 5 1
6 10 1

输出样例:
1 1
There‘s no weakness.
4 3

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;
const int N = 200010;

struct Seq
{
    int s, e, d; //起点 终点 公差
}seqs[N];

int n;

LL get_sum(int x)
{
    LL res = 0;
    for(int i = 0; i < n; i++)
        if(seqs[i].s <= x)
            //        上界              初始值(首项)  公差
            res += (min(seqs[i].e, x) - seqs[i].s) / seqs[i].d + 1; //*****
    return res;        
    
}

int main()
{
    int T;
    scanf("%d", &T);
    while(T--)
    {
        int l = 0, r = 0;
        scanf("%d", &n);
        for(int i = 0; i < n; i++)
        {
            int s, e, d;
            scanf("%d%d%d", &s, &e, &d);
            seqs[i] = {s, e, d};
            r = max(r, e); //最大值
        }
        while(l < r)
        {
            int mid = l + r >> 1;
            if(get_sum(mid) & 1) r = mid; // mid & 1等价于 mid % 2 是奇数,在左半边
            else l = mid + 1;
            
        }
        
        auto sum = get_sum(r) - get_sum(r - 1); //r个数
        if(sum % 2) printf("%d %lld\n", r, sum); //Linuxx系统用%lld Windows系统 %I64d
        else puts("There's no weakness.");
    }
    return 0;
}

121. 赶牛入圈

农夫约翰希望为他的奶牛们建立一个畜栏。
这些挑剔的畜生要求畜栏必须是正方形的,而且至少要包含C单位的三叶草,来当做它们的下午茶。
畜栏的边缘必须与X,Y轴平行。
约翰的土地里一共包含N单位的三叶草,每单位三叶草位于一个1 x 1的土地区域内,区域位置由其左下角坐标表示,并且区域左下角的X,Y坐标都为整数,范围在1到10000以内。
多个单位的三叶草可能会位于同一个1 x 1的区域内,因为这个原因,在接下来的输入中,同一个区域坐标可能出现多次。
只有一个区域完全位于修好的畜栏之中,才认为这个区域内的三叶草在畜栏之中。
请你帮约翰计算一下,能包含至少C单位面积三叶草的情况下,畜栏的最小边长是多少。

输入格式
第一行输入两个整数 C 和 N。
接下来 N 行,每行输入两个整数 X 和 Y,代表三叶草所在的区域的X,Y坐标。
同一行数据用空格隔开。

输出格式
输出一个整数,代表畜栏的最小边长。

数据范围
1≤C≤500,
C≤N≤500

输入样例:
3 4
1 2
2 1
4 1
5 2

输出样例:
4

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

typedef pair<int, int> PII;
const int N = 1010;

int n, C;
PII points[N];
vector<int> numbers; // 离散化后的数
int sum[N][N]; // 前缀和

int get(int x)
{
    int l = 0, r = numbers.size() - 1;
    while(l < r)
    {
        int mid = l + r >> 1;
        if(numbers[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return r;
}

bool check(int len)
{
    for(int x1 = 0, x2 = 1; x2 < numbers.size(); x2++)
    {
        while(numbers[x2] - numbers[x1 + 1] + 1 > len) x1++;
        for(int y1 = 0, y2 = 1; y2 < numbers.size(); y2 ++)
        {
            while(numbers[y2] - numbers[y1 + 1] + 1 >len) y1++;
            if(sum[x2][y2] - sum[x1][y2] - sum[x2][y1] + sum[x1][y1] >= C)
                return true;
        }
    }
    return false;
}

int main()
{
    cin >> C >> n;
    numbers.push_back(0);
    for(int i = 0; i < n; i++)
    {
        int x, y;
        cin >> x >> y;
        points[i] = {x, y};
        numbers.push_back(x);
        numbers.push_back(y);
    }
    
    sort(numbers.begin(), numbers.end());
    numbers.erase(unique(numbers.begin(), numbers.end()), numbers.end()); //把迭代器之间的相同元素放在最后,返回一个迭代器,
    //输出是最后一个不相同元素的位置,作用是删除重复元素
    for(int i = 0; i < n; i++)
    {
        //离散化
        int x = get(points[i].first), y = get(points[i].second);
        sum[x][y]++;
    }
    
    //前缀和
    for(int i = 1; i < numbers.size(); i++)
        for(int j = 1; j < numbers.size(); j++)
            sum[i][j] += sum[i-1][j] + sum[i][j - 1] - sum[i-1][j-1]; //矩形前缀和,二维前缀和
            
    int l = 1, r = 10000;
    while(l < r)
    {
        int mid = l + r >> 1;
        if(check(mid)) r = mid;
        else l = mid + 1;
    }
    
    cout << r << endl;
    return 0;
}

以上是关于《算法竞赛进阶指南》0.8总结与练习的主要内容,如果未能解决你的问题,请参考以下文章

《算法竞赛进阶指南》学习总结 二分与三分

算法竞赛进阶指南基本算法:递推与递归

算法竞赛进阶指南做题记录

《算法竞赛进阶指南》0x23剪枝 POJ1190上下界搜索与剪枝

算法竞赛进阶指南基础算法:前缀和与差分

《算法竞赛进阶指南》0x07贪心 POJ2054 color the tree树的缩点与合并