LGP12414 [YLLOI R1] 一路向北 学习笔记
LGP12414 [YLLOI R1] 一路向北 学习笔记
前言
有段时间没做洛谷绿题了,来一道。
鉴定为趣题。出得挺好!
题意简述
给定 \(n\) 个队列。每个队列一开始有 \(m\) 个正整数,第 \(i\) 个队列的第 \(j\) 个元素记为 \(a_{i,j}\)。其中 \(a_{i,1}\) 为队首,\(a_{i,m}\) 为队尾。
现在你手中有一个数字 \(0\)。你要选择一个队列将 \(0\) 放到队尾,并将其队首拿到手中。
接下来你将不断重复这个操作直到再次拿到 \(0\):
- 设你手中的数字为 \(p\)。将其放到第 \(p\) 个队列的队尾,并将其队首拿在手中。
问能不能在无限长的时间内不再次拿到 \(0\)。多测,共 \(T\) 组数据。
\(T\le 10\),\(\sum n\times m\le 10^5\)。
做法解析
你尝试手玩了些数据模拟,但一时半会没看出端倪,所以你考虑逆向思维。
考虑什么时候我们会再次拿到 \(0\)。更具体的,考虑到我们拿到 \(0\) 的那一瞬间发生了什么。
设一开始把 \(0\) 放在了序列 \(k\),则在我们手中拿着一个 \(k\) 并且 \(0\) 在 \(k\) 的队头的那一刻,我们就再次拿到了 \(0\)。
沿着这个场景进一步想,\(0\) 一开始在队尾,现在又在队头,这说明在我们开局操作之后我们又来了这 \(m\) 次,\(m-1\) 次是来拿 \(m\) 前面的元素的,第 \(m\) 次是来拿 \(0\) 自己的。你再想,我们每次来队列 \(k\) 都需要手上拿着一次 \(k\),而且这个 \(k\) 会被放到 \(m\) 后面不能再用,所以说如果开局把 \(0\) 放到 \(k\) 可以再次拿到 \(0\),那么全场至少得有 \(m\) 个 \(k\)。
所以反之,如果有某种元素 \(c\) 出现次数小于 \(m\),我们开局把 \(m\) 放到 \(c\) 就可以不拿到 \(0\)。我们得到了一个可以无限持续下去游戏的充分判定。
令人惊喜的,这也是必要判定。因为在无限长的时间内,所有元素都会趋向于回到自己编号的队列,而如果没有任何元素的出现次数小于 \(m\),就意味着每种元素都出现了 \(m\) 次,那么它们一定都会趋向于回到自己所属编号的队列而把场上所有队列挤满,你最后就一定会拿到 \(0\)。(作为对比,当我们可以无限进行游戏的时候,\(0\) 会苟在一个没被 \(k\) 号元素占满的队列 \(k\),然后手上拿的东西独立于 \(k\) 号队列无限循环进行游戏)
代码实现
#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
const int MaxN=1e5+5;
int N,M,X,buc[MaxN];
void mian(){
readis(N,M),fill(buc,buc+N+1,0);
for(int i=1;i<=N;i++)for(int j=1;j<=M;j++)readis(X),buc[X]++;
for(int i=1;i<=N;i++)if(buc[i]<M){puts("Yes");return;}
puts("No");
}
int Tcn;
int main(){
readi(Tcn);
while(Tcn--)mian();
return 0;
}
浙公网安备 33010602011771号