UOJ#6 NOI-2014 随机数生成器

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

AC通道-UOJ#6

题目读到最后才发现这道题目跟生成器没什么大关系…

题解

直入正题

前面的操作省略 直接讲数组生成之后

注意题目

因为题目当中有一句话叫做 路径排序之后字典序最小
所以这个时候我们就应该考虑如何维护其单调性
(脑中想的是:单调队列 差分约束 大小根堆 以及我不知道的高端操作…)

最后 我决定贪心…

由于每一个数字只出现一次 所以我们可以每一个数字都进行讨论

我们一定是要保证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);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值