【洛谷P2354】随机数生成器
题目
题目链接:https://www.luogu.com.cn/problem/P2354
小 H 最近在研究随机算法。随机算法往往需要通过调用随机数生成函数(例如 Pascal 中的 random 和 C/C++中的 rand)来获得随机性。事实上,随机数生成函数也并不是真正的“随机”,其一般都是利用某个算法计算得来的。
比如,下面这个二次多项式递推算法就是一个常用算法:
算法选定非负整数 \(x_0,a,b,c,d\) 作为随机种子,并采用如下递推公式进行计算。
对于任意 \(i ≥ 1,x_i=(a \times x_{i-1}^2+b \times x_{i-1}+c)\mod d\) 这样可以得到一个任意长度的非负整数数列\(\{x_i\},i \ge 1\),一般来说,我们认为这个数列是随机的。
利用随机序列 \({xi},i≥1\),我们还可以采用如下算法来产生一个 \(1\) 到 \(K\) 的随机排列$ { Ti },i=1 ... k$:
1、初始设 \(T\) 为 \(1\) 到 \(K\) 的递增序列;
2、对 \(T\) 进行 \(K\) 次交换,第 \(i\) 次交换,交换 \(T_i\) 和 \(T_{x_i \mod i + 1}\) 的值。
此外,小 H 在这 \(K\) 次交换的基础上,又额外进行了 \(Q\) 次交换操作,对于第i 次额外交换,小 H 会选定两个下标 \(u_i\) 和 \(v_i\),并交换 \(T_{u_i}\) 和 \(T_{v_i}\) 的值。
为了检验这个随机排列生成算法的实用性,小 H 设计了如下问题:
小 H 有一个 \(N\) 行 \(M\) 列的棋盘,她首先按照上述过程,通过 \(N \times M + Q\) 次交换操作,生成了一个 \(1\sim N \times M\) 的随机排列 \(\{Ti\},i=1 ... N \times M\),然后将这 \(N \times M\) 个数逐行逐列依次填入这个棋盘:也就是第 \(i\) 行第 \(j\) 列的格子上所填入的数应为 $ T_{(i-1) \times M+u_j} $。
接着小 H 希望从棋盘的左上角,也就是第一行第一列的格子出发,每次向右走或者向下走,在不走出棋盘的前提下,走到棋盘的右下角,也就是第 \(N\) 行第 \(M\) 列的格子。
小 H 把所经过格子上的数字都记录了下来,并从小到大排序,这样,对于任何一条合法的移动路径,小 H 都可以得到一个长度为 \(N + M - 1\) 的升序序列,我们称之为路径序列。
小 H 想知道,她可能得到的字典序最小的路径序列应该是怎样的呢?
思路
这道题是真的迷。。。前面叫你做一大堆事情就是无用的模拟。。。
可能用来卡空间??
按照题目说的方法可以得出一个\(n\times m\)的矩阵,然后我们需要在矩阵从\((1,1)\)到\((n,m)\)的路径中找一条路径,使得这条路径每一个格子的权值排序之后字典序最小。
由于是排序之后字典序最小,所以我们可以从小到大枚举数字,然后判断它是否能加入路径中。
显然,一个数字可以加入路径中的必要条件是在这个格子的左下方、右上方均没有格子被选中。这个很好理解。
那么我们就维护\(L[i]\)表示\(i\sim n\)行所有被选中的格子的最小纵坐标,\(R[i]\)表示\(1\sim i\)行所有被选中格子的最大纵坐标。那么每次判断一个格子是否可以加入是\(O(1)\)的,修改是\(O(n)\)的,但是我们只有\(n+m-1\)个格子会加进路径,且仅有加进路径的格子会修改,所以修改的总复杂度也是\(O(nm)\)(设\(n,m\)同阶)的。
一开始没有想到只会更新\(n+m-1\)次就写了线段树\(O(\log n)\)修改和查询,这样查询的复杂度是\(O(nm\log m)\)的了。。。
时间复杂度\(O(nm)\)。
这道题会略卡空间,只能开下\(2\)个\(5000*5000\)的\(int\)数组,而考虑到每一个点的位置都在\([1,5000]\)内,可以用\(short\)存下,这样空间就不会炸了。
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=5001;
int n,m,cnt,Q,t[N*N];
short pos[N*N][2],R[N],L[N];
ll x,a,b,c,d;
int main()
{
scanf("%lld%lld%lld%lld%lld",&x,&a,&b,&c,&d);
scanf("%d%d%d",&n,&m,&Q);
for (register int i=1;i<=n*m;i++)
t[i]=i;
for (register int i=1;i<=n*m;i++)
{
x=(x*x*a%d+x*b%d+c)%d;
swap(t[i],t[x%i+1]);
}
for (register int i=1;i<=Q;i++)
{
int u,v;
scanf("%d%d",&u,&v);
swap(t[u],t[v]);
}
for (register int i=1;i<=n;i++)
for (register int j=1;j<=m;j++)
{
pos[t[(i-1)*m+j]][0]=i;
pos[t[(i-1)*m+j]][1]=j;
}
for (int i=1;i<=n;i++)
L[i]=m,R[i]=1;
for (register int i=1;i<=n*m;i++)
{
if (pos[i][0]>1 && R[pos[i][0]-1]>pos[i][1]) continue;
if (pos[i][0]<n && L[pos[i][0]+1]<pos[i][1]) continue;
printf("%d ",i);
cnt++;
if (cnt==n+m-1) return 0;
for (int j=pos[i][0];j<=n;j++) R[j]=max(R[j],pos[i][1]);
for (int j=pos[i][0];j>=1;j--) L[j]=min(L[j],pos[i][1]);
}
}