算法竞赛进阶指南0x08 总结练习(上)

飞行员兄弟

“飞行员兄弟”这个游戏,需要玩家顺利的打开一个拥有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<cstring>
#include<vector>

#define pii pair<int,int>

using namespace std;

const int N=10;

int f;
int change[N][N];

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

int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    
    for(int i=0;i<4;i++)
    {
        char str[N];
        scanf("%s",str);
        for(int j=0;j<4;j++)
            if(str[j]=='+')
                f|=1<<get(i,j);
    }

    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);
            }
        }

    int t=f;
    vector<pii> res,temp;
    for(int i=0;i<1<<16;i++)
    {
        temp.clear();
        f=t;
        for(int j=0;j<16;j++)
            if(i>>j&1)
            {
                f^=change[j/4][j%4];
                temp.push_back({j/4,j%4});
            }
        if(!f&&(res.empty()||temp.size()<res.size()))
            res=temp;
    }
    cout<<res.size()<<endl;
    for(auto t:res)
        cout<<t.first+1<<" "<<t.second+1<<endl; 
    


    return 0;
}

占卜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对称作”极凶”,12对为“大凶”,3对为“凶”,45对为“小凶”,6对为“中庸”,78对“小吉”,9对为“吉”,1011为“大吉”,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<cstring>
#include<algorithm>
#include<vector>

using namespace std;

const int N=15;

int have[N];
vector<int> card[N];

inline int get(char ch)
{
    if(ch=='0') return 10;
    else if(ch=='J') return 11;
    else if(ch=='Q') return 12;
    else if(ch=='K') return 13;
    else if(ch=='A') return 1;
    else return ch-'0';
}

int main(){
    //freopen("data.in","r",stdin);
    freopen("data.out","w",stdout);
    for(int i=1;i<=13;i++)
    {
        char str[2];
        for(int j=1;j<=4;j++)
        {
            scanf("%s",str);
            card[i].push_back(get(str[0]));
        }
    }

    while(card[13].size())
    {
        int get=card[13].back();
        card[13].pop_back();
        while(get!=13)
        {
            have[get]++;
            
            int temp=card[get].back();
            card[get].pop_back();
            get=temp;
        }
    }

    int res=0;
    for(int i=1;i<=12;i++)
        if(have[i]==4)
            res++;
    cout<<res<<endl;

    return 0;
}

分形

分形,具有以非整数维形式充填空间的形态特征。

通常被定义为“一个粗糙或零碎的几何形状,可以分成数个部分,且每一部分都(至少近似地)是整体缩小后的形状”,即具有自相似的性质。

现在,定义“盒子分形”如下:

一级盒子分形:

   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
-

这题还是比较右意思的,用到了分形思想,要解决当前问题由5个子问题构成,还是比较容易的,来看代码。

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

using namespace std;

const int N=5050;

char g[N][N];

void dfs(int n)
{
    if(n==1)
    {
        g[0][0]='X';
        return ;
    }
    dfs(n-1);
    int len=pow(3,n-2);
    for(int i=0;i<len;i++)
        for(int j=0;j<len;j++)
        {
            g[i+2*len][j]=g[i][j];
            g[i+len][j+len]=g[i][j];
            g[i+2*len][j+2*len]=g[i][j];
            g[i][j+2*len]=g[i][j];
        }

}

int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);

    dfs(7);
    int n;
    while(cin>>n,~n)
    {
        int len=pow(3,n-1);
        for(int i=0;i<len;i++)
        {
            for(int j=0;j<len;j++)
            {
                if(g[i][j]=='X') printf("X");
                else printf(" ");
            }
            puts("");
        }
        puts("-");
    }

    return 0;
}

袭击

在与联盟的战斗中屡战屡败后,帝国撤退到了最后一个据点。

依靠其强大的防御系统,帝国击退了联盟的六波猛烈进攻。

经过几天的苦思冥想,联盟将军亚瑟终于注意到帝国防御系统唯一的弱点就是能源供应。

该系统由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

这是一道比较经典的一题,很容易想到的一种方式就是暴力逐一比较,但这个数据范围就劝退,105 ,O(n2)一定会TLE。我们要想一种O(nlogn)的方法。其实我们可以用归并+贪心优化成O(nlogn)。
如果这个问题使一维的,我们很容易想到先排序,然后再用O(n)的方法找到最小值。在这里插入图片描述
我们一定会这样挨个比较:(1,2)(2,3)(3,4)(4,5)(5,6)
从这几组数中选一个最小值,因为和显然(1,3)一定会大于(1,2),那么我们能再二维中寻找出相同的性质吗?当然可以

我们先对行进行排序,然后分治,发现满足最小值的点就这几种而已。
在这里插入图片描述
每一组内的距离最小的点,相邻两组内最小的点,最小的两个点一定使从这两种情况中选一个更小的。如果用归并的思想第一种情况都不需要我们处理,我们可以把它交给递归处理,那第二种怎么办呢?我们可以用递归先处理出两块的最小值,然后我们暴力枚举这样一个正方形里的点。正方形的边长为刚刚递归处理的最小值。这样我们这题就结束了。
在这里插入图片描述
虽然这个正方形里的点是要用N2处理的但是一般里面的点稀疏。
提问:这种做法有没有问题呢?当然有了~ 。~
如果正方形里的点过多一样会TLE,但是非常少见,一般不会出现这样的情况(特殊情况:出题人特意卡我们)那怎么办呢?出题人出招了,我们也要有相应的应对策略,我们可以将所有点旋转一个随机的角度,做一个预处理,这样他们就很难卡我们了。
提问:那有没有真正完美的nlogn的方法呢?有的,大家可以上网搜索我连那个算法的名字都忘了hh
一般情况下我们这个做法已经很完美了~ .~我们来看代码。。。

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

using namespace std;

const int N=200010;
const double INF=1e10;
struct Point{
    int x,y;
    int owner;
    bool operator<(const Point &a) const{
        return x<a.x;
    }
};

Point point[N],t1[N];

double dict(const Point &a,const Point &b)
{
    if(a.owner==b.owner) return INF;
    double dx=1.0*a.x-b.x;
    double dy=1.0*a.y-b.y;
    return sqrt(dx*dx+dy*dy);
}

double merge(int l,int r)
{
    if(l>=r) return INF;
    int mid=l+r>>1;
    int mid_x=point[mid].x;
    double res=min(merge(l,mid),merge(mid+1,r));
    {
        int i=l,j=mid+1;
        for(int k=l;k<=r;k++)
        { 
            if(j>r||(i<=mid&&(point[i].y<point[j].y))) t1[k]=point[i++];
            else t1[k]=point[j++];
        }
        for(int k=l;k<=r;k++)
            point[k]=t1[k];
    }
    int k=0;
    for(int i=l;i<=r;i++)
        if(point[i].x>mid_x-res&&point[i].x<mid_x+res)
            t1[k++]=point[i];
    for(int i=0;i<k;i++)
        for(int j=i-1;j>=0&&t1[i].y-t1[j].y<res;j--)
            res=min(res,dict(t1[i],t1[j]));

    return res;
}
int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n;
        scanf("%d",&n);
        for(int i=0;i<n;i++)
            scanf("%d%d",&point[i].x,&point[i].y),point[i].owner=1;
        for(int i=n;i<2*n;i++)
            scanf("%d%d",&point[i].x,&point[i].y),point[i].owner=2;
        sort(point,point+2*n);
        printf("%.3lf\n",merge(0,2*n-1));
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值