2020冬牛客训练第二场部分题题解

为什么我没有爆零呢,我觉得大概率是出题人昨天意识到题太难了,临时找了一道题让我这样的fw能签个到。对这种事还是要心存感激的。这题真是难的离天下之大谱

以下顺序与难度无关反正我都不会,那就都是难了
顺便,这份题解就是官方题解 + 一点自己的解释

A 牛牛与牛妹的RMQ

B 牛牛抓牛妹

给定一个 n ( 2 ≤ n ≤ 100 ) n(2\leq n\leq 100) n(2n100)个点, m ( 0 ≤ m ≤ 500 ) m(0 \leq m \leq 500) m(0m500)个边。你的对手操纵一颗棋子想从起点走到重点。你可以控制图中 k ( 0 ≤ k ≤ 7 ) k(0\leq k \leq 7) k(0k7)个关卡的状态(封锁或可通行)。当关卡处于封锁状态时,棋子不能再移动到该节点。但是如果棋子已经位于关卡上面,不会立刻受到影响,但是在离开该节点后无法再返回处于封锁状态的节点。 你的对手每回合都会寻找当前位置到终点的最短路线移动,如果最短路线不唯一,她总是会选择移动到节点编号较小的节点。
你可以在对手移动之前进行操作,改变关卡节点的状态(封锁关卡或者通行)。如果在你操作之后,从对手到终点n不存在任何一条可行的通路,就认为你困住了队首,此时就认为你胜利了。

这个题意我真的概括不明白,还是看题去吧,还有有图解 [传送门]

一道伪交互题。
当时在场上虽然有些思路,但显然要做上来这道题需要懂得更多。
按照出题人的说法,需要的前置知识有这些:

  • 图论最短路
  • 二进制状压(会状压dp或状压搜索)
  • 生成树(最好是prim)

假设已经掌握了这些知识,来看看这道题的思路吧(其实是出题人的思路) 。

1.数据规模

我们看一看数据规模, n ≤ 100 , k ≤ 7 n\leq 100, k\leq 7 n100,k7,这已经允许复杂度较高的算法了(大概率也是找不到复杂度低的算法吧)。

2.分层图

出题人给的题解里管这玩意叫分层图,但我感觉并不合适。我最开始还以为这个分层图是像dinic那样跑bfs给图分层呢。反正你需要知道,下面说的分层图可能和你以前听说过的不一样就对了。

什么是分层图呢?就是说对于一个图中有一些“开关”或者是其他能够导致图出现变化的“动态图”问题。

比如这道题,如果给关卡置为开/关状态,那么就得到了两张不同的图。
蓝色的节点即为*关卡*
上图中,蓝色和红色的节点即为关卡。蓝色表示可通行,红色表示不可通行。

处理这种问题,一个可行的方案是(以本题为例):如果 n × 2 k n \times 2^k n×2k的大小是可以接受的,那么就为 2 k 2^k 2k种不同的状态各建一个图。

注意: 2 k 2^k 2k种不同的状态各建一个图 这种说法并不严谨,首先,我们最后需要的并不是 2 k 2^k 2k张不同的图,而是我们下文提到的最短路生成树。严谨的来说,我们是要通过一张图,枚举所有可能出现的状态,并建出最短路生成树。(也就是说,图只保存一张,但最短路生成树需要 2 k 2^k 2k个)。具体实现,因为我并没有学过状压,可能这个实现对学过状压的是基操?总之,具体实现请见标程
在这里插入图片描述

最短路生成树

每回合都会寻找当前位置到终点的最短路线移动,如果最短路线不唯一,她总是会选择移动到节点编号较小的节点。

如果图连通,那么除了终点以外,每个点都应该有一个后继。 n n n个点, n − 1 n-1 n1个后继。也就是说,应该有 n − 1 n-1 n1条边,维护点和后继的关系,即: n n n个点, n − 1 n-1 n1条边

当听到 n n n个点, n − 1 n-1 n1条边的时候,DNA就该有反应了
——by 该比赛出题人

这是一棵树,并且是以图的终点为根。在图上任何一个可以到达终点的位置,沿着这棵树上的边走到终点,就可以保证走的是最短路。并且,由于题中的要求:

如果最短路线不唯一,她总是会选择移动到节点编号较小的节点。

沿着树边的一条路径不仅是最短路,而且在有多种选择的情况下一定会到节点编号较小的节点。

然后就该是写代码实现找到这棵树了:具体的方法类似prim求最小生成树,你只需要以终点作为起点做bfs,记录每个节点的后继,然后取min。

最后求得的树应该是长这样:
在这里插入图片描述
最后被指向的终点是树的根节点。

那这棵树有什么用呢?按照我的理解,这棵树给定了图在某种状态下你的对手如何行动:一定沿着这棵生成树的树边行走

需要注意到,上面这张图的一些点在某些状态的图中是没有后继的,即连通。这就是我们想要的,我们需要通过开/关关卡来将对手关在那样的地方。

重建图-状态转移边

上面我们说过,最短路径生成树给出了图在某种状态下你的对手如何行动。但是,你的对手并非在一个确定状态的图上行动。你希望将他关住,就需要关闭一些关卡,这必然会使状态改变。我们现在已经有了不同状态下的图,而开/关关卡应该是一条连接不同状态图的边,使得对手从一种状态转移到另一种状态。

下面这张图给出了一些状态转移边(并不是所有的):
在这里插入图片描述
比如,关闭 s t a t e = 000 state=000 state=000的图上的6号关卡,状态就转移到了 s t a t e = 010 state=010 state=010。所以 s t a t e = 000 state=000 state=000的每个节点上都有一条到 s t a t e = 010 state=010 state=010的状态转移边,转移到 s t a t e = 010 state=010 state=010上与自己对应的节点。(比如1号会转移到19号,3号会转移到21号,5号会转移到23号)

同样的,为了方便理解(方便我自己理解 ),上面采用的依旧是不严谨的表述。具体实现详见标程

搜索回溯

现在我们已经重新建好了一个图:包含生成树边,状态转移边。我们需要的就是搜索从起点到那些没有后继(与终点不连通的节点)的路径,如果经过一条生成树边,就对应一个continue,如果经过一条状态转移边,就对应一个lock/unlock

代码

拜读一下标程吧。[传送门]

C 牛牛与字符串border

D 牛牛与整除分块

E 牛牛与跷跷板

F 牛牛与交换排序

给定一个数组,数组元素是从 1 1 1 n ( 1 ≤ n ≤ 1 0 5 ) n(1\leq n \leq 10^5) n(1n105)的排列。问是否能够确定一个 k ( 1 ≤ k ≤ n ) k(1\leq k \leq n) k(1kn),使得可以经过任意次翻转一个长为 k k k的子数组将整个数组排好序?要求下一次翻转的区间左端点一定大于等于上一次翻转选择的左端点。
这个题思维上不是很大,估计会平衡树的人不多,应该不会有人想不开上个平衡树吧?
——by 该比赛出题人

上面的最后一句话是我加上的,原本是写在官方题解里的。

解这道题可以分为两个步骤:
首先确定 k k k。这题虽然给了spj,但其实是迷惑你的。除非给出的数组已经有序,否则 k k k就是确定的(还真给我唬住了 )。

因为需要排好序,那么1必须排在最前面,你能进行的操作只有区间翻转,并且要求下一次翻转的区间左端点一定大于等于上一次翻转选择的左端点。那你就必须直接把1翻转到1的位置(如果1已经在自己的位置上了,那就找2,以此类推)。这样就可以确认 k k k

其次就是判在确定的 k k k值下,是否能通过翻转将数组排序。
这里用了一个黑科技:支持翻转操作的双端队列。注意这个翻转操作是整个翻转而非区间翻转。翻转操作可以通过在双端队列上加一个翻转lazytag实现。(其实如果不追求速度,这个思路维护区间翻转好像也不是不行。但正经维护区间翻转的还得是平衡树)如果有lazytag,那么索引队首元素,队首元素出队这些就从队尾走,反之亦然。

以下为维护该双端队列的一些函数。

void reverse()
{
    rev^=1;
}

void push_front(int x)
{
    rev?buffer[++tail]=x:buffer[--head]=x;
}

void push_back(int x)
{
    rev?buffer[--head]=x:buffer[++tail]=x;
}

void pop_back()
{
    rev?head++:tail--;
}

void pop_front()
{
    rev?tail--:head++;
}

有了这个黑科技,就可以判 k k k是否合适了。
思路:始终保持队列中有 k k k个元素,如果顺序不对就翻转,如果翻转之后还不对那就是 k k k的问题了,然后把已经在自己位置上的数出队。然后后续元素入队补满 k k k个……
以下为核心代码:

for(int i=1;i<=k;++i)
{
    q.push_back(a[i]);
}
for(int i=1;i<=n;++i)
{
    if(q.front()!=i&&q.size()==k)q.reverse();
    if(q.front()!=i)
    {
        flag=false; // 没有合适的k
        break;
    }
    q.pop_front();
    if(i+k<=n)
    {
        q.push_back(a[i+k]);
    }
}

H 牛牛与棋盘

签到题,读准题意,判奇偶即可
唯一会的一道题,还为了稳,8分钟才交上去

I 牛牛的“质因数”

J 牛牛想要成为hacker

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值