6. 【NOI2014】随机数生成器
题目描述
小H最近在研究随机算法。随机算法往往需要通过调用随机数生成函数(例如Pascal中的random和C/C++中的rand)来获得随机性。事实上,随机数生成函数也并不是真正的“随机”,其一般都是利用某个算法计算得来的。
比如,下面这个二次多项式递推算法就是一个常用算法:
算法选定非负整数x0,a,b,c,dx0,a,b,c,d作为随机种子,并采用如下递推公式进行计算。
对于任意i≥1,xi=(ax2i−1+bxi−1+c)moddi≥1,xi=(axi−12+bxi−1+c)modd。
这样可以得到一个任意长度的非负整数数列{xi}i≥1{xi}i≥1,一般来说,我们认为这个数列是随机的。
利用随机序列{xi}i≥1{xi}i≥1,我们还可以采用如下算法来产生一个11到KK的随机排列{Ti}Ki=1{Ti}i=1K:
初始设TT为11到KK的递增序列;
对TT进行KK次交换,第ii次交换,交换TiTi和T(ximodi)+1T(ximodi)+1 的值。
此外,小H在这KK次交换的基础上,又额外进行了QQ次交换操作,对于第ii次额外交换,小H会选定两个下标uiui和vivi,并交换TuiTui和TviTvi的值。
为了检验这个随机排列生成算法的实用性,小H设计了如下问题:
小H有一个NN行MM列的棋盘,她首先按照上述过程,通过N×M+QN×M+Q次交换操作,生成了一个 1∼N×M1∼N×M的随机排列{Ti}N×Mi=1{Ti}i=1N×M然后将这N×MN×M个数逐行逐列依次填入这个棋盘:也就是第 ii 行第 jj 列的格子上所填入的数应为
T(i−1)×M+jT(i−1)×M+j。
接着小H希望从棋盘的左上角,也就是第一行第一列的格子出发,每次向右走或者向下走,在不走出棋盘的前提下,走到棋盘的右下角,也就是第NN行第MM列的格子。
小H把所经过格子上的数字都记录了下来,并从小到大排序,这样,对于任何一条合法的移动路径,小H都可以得到一个长度为N+M−1N+M−1的升序序列,我们称之为路径序列。
小H想知道,她可能得到的字典序最小的路径序列应该是怎样的呢?
数据规模及约定
2≤N,M≤5000
0≤Q≤50000
0≤a≤300
0≤b,c≤108
0≤x0<d≤108
1≤ui,vi≤N×M
题目读到最后才发现这道题目跟生成器没什么大关系…
题解
直入正题
前面的操作省略 直接讲数组生成之后
注意题目
因为题目当中有一句话叫做 路径排序之后字典序最小
所以这个时候我们就应该考虑如何维护其单调性
(脑中想的是:单调队列 差分约束 大小根堆 以及我不知道的高端操作…)
最后 我决定贪心…
由于每一个数字只出现一次 所以我们可以每一个数字都进行讨论
我们一定是要保证1在内的
所以我们先要往一个空的棋盘里加入一个1
然后我们就很贪心地希望2也在里头
所以我们就想知道 2 能不能在1所在的路径上呢?
约束条件
所以我们此时需要更加仔细地读题
题目当中说道 路径的下一个点只能往右/下走
也就是说 路径中的任何一个点和其它点的关系是
要么在这个点的左上方
要么在这个点的右下方
所以 我们在加入2的时候 需要判断2相对于1的位置
满足上述条件 就加入路径
进一步贪心
加了多个点之后 又怎么对当前点进行判定呢?
假如当前点在第x行的第y个位置
那么对于1到x-1行 所有数字的位置都应该<=y
对于 x+1到n行 所有的数字的位置都应该>=y
所以有人(不是我)就想到了一个骚操作
开两个数字 high[x] 和 low[x]
high[x]记录 x+1 到 n 行 所有路径上的数字的最小位置
low[x]记录 1 到 x-1 行 所有路径上的数字的最大位置
那么对于位置 x行 y列
若y>=high[x]&&y<=low[x] 则该点可以放到路径上
然后再对high和low进行更新
对于xi<x high[xi]=min(high[xi],y)(因为当前点在xi的下方)
对于xi>x low[xi]=max(low[xi],y)(因为当前点在xi的上方)
于是我们就成功地贪心出了一条路径
注意
x数组在生成的时候可能会存在超int的情况 因此要用 1LL 之类的操作来避免超int
code(欢迎hack)
#include <iostream>
#include <cstdio>
using namespace std;
int N,M,m,x[25001234],T[25001234];
int low[5123],high[5123];
inline int input()
{
char c=getchar();int o;
while(c>57||c<48)c=getchar();
for(o=0;c>47&&c<58;c=getchar())o=(o<<1)+(o<<3)+c-48;
return o;
}
bool add(int nx,int ny)
{
for(int i=1;i<=N;i++)
if(i<ny)high[i]=min(nx,high[i]);
else if(i>ny)low[i]=max(nx,low[i]);
}
int main()
{
// freopen("In.txt","r",stdin);
long long a,b,c,d,Q,tot=0,NM;
x[0]=input();a=input();b=input();c=input();d=input();
N=input();M=input();Q=input();m=M-1;NM=N*M;
for(int i=1;i<=N;i++)high[i]=M;
for(int i=1;i<=NM;i++)x[i]=(x[i-1]*(a*x[i-1]+b)+c)%d,T[i]=i;
for(int i=1;i<=NM;i++)swap(T[i],T[x[i]%i+1]);
for(int i=1;i<=Q;i++)
{
a=input();b=input();
swap(T[a],T[b]);
}
for(int i=1;i<=NM;i++)x[T[i]]=i;
for(int i=1,p=x[i];i<=NM;i++,p=x[i])
{
int nx=p%M,ny=(p+m)/M;
if(!nx)nx=M;
if(nx>=low[ny]&&nx<=high[ny])
{
add(nx,ny);
if(++tot==N+M-1)printf("%d",i);
else printf("%d ",i);
}
}
}