[NOI2014]随机数生成器 题解

原题网址:
bzoj P3671
洛谷 P2354
附原题:

时间限制 内存限制
1.00s ~ 5.00s 250.00MB

题目描述

小 H 最近在研究随机算法。随机算法往往需要通过调用随机数生成函数(例如 Pascal 中的 random 和 C/C++中的 rand)来获得随机性。事实上,随机数生成函数也并不是真正的“随机”,其一般都是利用某个算法计算得来的。
比如,下面这个二次多项式递推算法就是一个常用算法:
算法选定非负整数x0,a,b,c,d作为随机种子,并采用如下递推公式进行计算。
对于任意i1 $x_i==(a×x+b×x_{i-1}^2+c) mod d 这样可以得到一个任意长度的非负整数数列 xi,i1,一般来说,我们认为这个数列是随机的。
利用随机序列xi,i1,我们还可以采用如下算法来产生一个1K 的随机排列Ti,i=1...k
1、初始设T1K的递增序列;
2、对T进行K次交换,第i次交换,交换TiTximod i+1
此外,小 H 在这K次交换的基础上,又额外进行了Q次交换操作,对于第i 次额外交换,小 H 会选定两个下标uivi,并交换TviTvi的值
为了检验这个随机排列生成算法的实用性,小 H 设计了如下问题:
小 H 有一个 N 行 M 列的棋盘,她首先按照上述过程,通过N×M+Q次交换操作,生成了一个1N×MN×M的随机排列,Ti,i=1...N×M。然后将这N×M个数逐行逐列依次填入这个棋盘:也就是第i行第j列的格子上所填入的数应为T(i1)×M+uj
接着小 H 希望从棋盘的左上角,也就是第一行第一列的格子出发,每次向右走或者向下走,在不走出棋盘的前提下,走到棋盘的右下角,也就是第 N行第M列的格子。
小 H 把所经过格子上的数字都记录了下来,并从小到大排序,这样,对于任何一条合法的移动路径,小 H 都可以得到一个长度为N+M1的升序序列,我们称之为路径序列。
小 H 想知道,她可能得到的字典序最小的路径序列应该是怎样的呢?

输入格式

第1行包含5个整数,依次为x0,a,b,c,d描述小H采用的随机数生成算法所需的随机种子。
第2行包含三个整数N,M,Q表示小H希望生成一个1到N×M 的排列来填入她NM列的棋盘,并且小H在初始的N×M次交换操作后,又进行了Q次额外的交换操作。
接下来Q行,第i行包含两个整数ui,vi,表示第i次额外交换操作将交换Tui,Tvi的值。

输出格式

输出一行,包含N+M1个由空格隔开的正整数,表示可以得到的字典序最小的路径序列。

输入输出样例

输入
1 3 5 1 71
3 4 3
1 7
9 9
4 9
输出
1 2 6 8 9 12

数据范围

在这里插入图片描述

题解内容

终于把题目写完了,markdown写了半个多小时
前面blablabla讲了一大堆废话
一句话讲,就是教你怎么生成一个随机函数,代码直接按照它的做法写下来,注意数据范围,要加longlong不然会直接RE就像我一样:
在这里插入图片描述
int了,所以加上longlong强制转换,就可以了~~~~~~

scanf("%d%d%d%d%d",&x[0],&a,&b,&c,&d);
scanf("%d%d%d",&n,&m,&q);
int s=n*m;
int i,j,tmp,tmps;
for(i=1;i<=s+1;i++)
x[i]=((long long)a*x[i-1]*x[i-1]+(long long)b*x[i-1]+c)%d;
for(i=1;i<=s;i++) t[i]=i;//注意这里的(longlong),不加会炸掉的
for(i=1;i<=s;i++){
tmps=x[i]%i+1;
tmp=t[i];
t[i]=t[tmps];
t[tmps]=tmp;
}
for(i=1;i<=q;i++){
scanf("%d%d",&a,&b);
tmp=t[a];
t[a]=t[b];
t[b]=tmp;
}

重头戏在后面,明显是一道贪心,优先考虑1,然后是2,明显最外层的循环是:

for(int i=1;i<=n*m;i++)

当然,register加不加都可以的
然后就是空间上了,这题的空间开两个5000×5000的数组是足够的,但是,建议使用一维数组,同时,如果需要三个这样的数组,可以把另一个不需要的数组拿来用,记得清空。

我的做法是先开一个数组来记录总每个数字的编码,位置编码如下:

1 2 3 4
5 6 7 8
9 10 11 12

用数组xi来表示第i个数的位置,然后建立两个数组down和up,如果取了一个数,就在上面做标记。比如说当6号位被取之后,那么9号位、3号位、4号位就不能取了,其中,upi,downi表示第i列可行的区间即可。每次枚举的时候就可以判定,如果可以,就更新,如果不可以就直接跳过。
注意这道题目需要很多的细节。

#include<cstdio>
#include<cmath>
#include<iostream>
#define maxn 5000*5000+39
using namespace std;
int t[maxn],x[maxn];
int up[5039],down[5039];
int a,b,c,d,n,m,q;
int main(){
scanf("%d%d%d%d%d",&x[0],&a,&b,&c,&d);
scanf("%d%d%d",&n,&m,&q);
int s=n*m;
int i,j,tmp,tmps;
for(i=1;i<=s+1;i++)
x[i]=((long long)a*x[i-1]*x[i-1]+(long long)b*x[i-1]+c)%d;
for(i=1;i<=s;i++) t[i]=i;
for(i=1;i<=s;i++){
tmps=x[i]%i+1;
tmp=t[i];
t[i]=t[tmps];
t[tmps]=tmp;
}
for(i=1;i<=q;i++){
scanf("%d%d",&a,&b);
tmp=t[a];
t[a]=t[b];
t[b]=tmp;
}//Insize t
for(i=1;i<=s;i++)
x[t[i]]=i;
for(i=1;i<=m;i++)
up[i]=0,down[i]=n+1;//Insize up and down
int line,row;
for(i=1;i<=s;i++){
line=ceil(1.0*x[i]/m);
if(x[i]%m==0)
row=m;
else row=x[i]%m;//line 行 row 列
if(up[row]<line&&line<down[row]){
printf("%d ",i);
for(j=1;j<=row-1;j++)
down[j]=min(down[j],line+1);
for(j=row+1;j<=m;j++)
up[j]=max(line-1,up[j]);
}
}
return 0;
}
posted @   jiangtaizhe001  阅读(83)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示