原题网址:
bzoj P3671
洛谷 P2354
附原题:
时间限制 | 内存限制 |
---|---|
1.00s ~ 5.00s | 250.00MB |
小 H 最近在研究随机算法。随机算法往往需要通过调用随机数生成函数(例如 Pascal 中的 random 和 C/C++中的 rand)来获得随机性。事实上,随机数生成函数也并不是真正的“随机”,其一般都是利用某个算法计算得来的。
比如,下面这个二次多项式递推算法就是一个常用算法:
算法选定非负整数 x 0 , a , b , c , d x_0,a,b,c,d x0,a,b,c,d作为随机种子,并采用如下递推公式进行计算。
对于任意 i ≥ 1 i \geq 1 i≥1 $x_i==(a×x+b×x_{i-1}^2+c) mod d d d 这样可以得到一个任意长度的非负整数数列 x i , i ≥ 1 {x_i,i\geq1} xi,i≥1,一般来说,我们认为这个数列是随机的。
利用随机序列 x i , i ≥ 1 {xi},i\geq1 xi,i≥1,我们还可以采用如下算法来产生一个 1 1 1到 K K K 的随机排列 T i , i = 1... k {T_i},i=1...k Ti,i=1...k
1、初始设 T T T为 1 1 1到 K K K的递增序列;
2、对 T T T进行 K K K次交换,第 i i i次交换,交换 T i T_i Ti和 T x i T_{x_i} Tximod i + 1 _{i+1} i+1
此外,小 H 在这 K K K次交换的基础上,又额外进行了 Q Q Q次交换操作,对于第i 次额外交换,小 H 会选定两个下标 u i 和 v i u_i和v_i ui和vi,并交换 T v i 和 T v i T_{v_i}和T_{v_i} Tvi和Tvi的值
为了检验这个随机排列生成算法的实用性,小 H 设计了如下问题:
小 H 有一个 N 行 M 列的棋盘,她首先按照上述过程,通过 N × M + Q N \times M + Q N×M+Q次交换操作,生成了一个 1 ∼ N × M ∼ N × M 1\sim N \times M∼N\times M 1∼N×M∼N×M的随机排列, T i , i = 1... N × M T_i,i=1...N \times M Ti,i=1...N×M。然后将这 N × M N \times M N×M个数逐行逐列依次填入这个棋盘:也就是第 i i i行第 j j j列的格子上所填入的数应为 T ( i − 1 ) × M + u j T_{(i-1)\times M+u_j} T(i−1)×M+uj。
接着小 H 希望从棋盘的左上角,也就是第一行第一列的格子出发,每次向右走或者向下走,在不走出棋盘的前提下,走到棋盘的右下角,也就是第 N N N行第 M M M列的格子。
小 H 把所经过格子上的数字都记录了下来,并从小到大排序,这样,对于任何一条合法的移动路径,小 H 都可以得到一个长度为 N + M − 1 N + M - 1 N+M−1的升序序列,我们称之为路径序列。
小 H 想知道,她可能得到的字典序最小的路径序列应该是怎样的呢?
第1行包含5个整数,依次为 x 0 , a , b , c , d x_0,a,b,c,d x0,a,b,c,d描述小H采用的随机数生成算法所需的随机种子。
第2行包含三个整数 N , M , Q N,M,Q N,M,Q表示小H希望生成一个1到 N × M N \times M N×M 的排列来填入她 N N N行 M M M列的棋盘,并且小H在初始的 N × M N \times M N×M次交换操作后,又进行了 Q Q Q次额外的交换操作。
接下来 Q Q Q行,第 i i i行包含两个整数 u i , v i u_i,v_i ui,vi,表示第 i i i次额外交换操作将交换 T u i , T v i T_{u_i},T_{v_i} Tui,Tvi的值。
输出一行,包含 N + M − 1 N+M-1 N+M−1个由空格隔开的正整数,表示可以得到的字典序最小的路径序列。
输入
1 3 5 1 71
3 4 3
1 7
9 9
4 9
输出
1 2 6 8 9 12
终于把题目写完了,markdown写了半个多小时
前面blablabla讲了一大堆废话
一句话讲,就是教你怎么生成一个随机函数,代码直接按照它的做法写下来,注意数据范围,要加 l o n g l o n g long long longlong不然会直接RE就像我一样:
炸 i n t int int了,所以加上 l o n g l o n g long long 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 5000\times5000 5000×5000的数组是足够的,但是,建议使用一维数组,同时,如果需要三个这样的数组,可以把另一个不需要的数组拿来用,记得清空。
我的做法是先开一个数组来记录总每个数字的编码,位置编码如下:
1 | 2 | 3 | 4 |
---|---|---|---|
5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 |
用数组 x i x_i xi来表示第 i i i个数的位置,然后建立两个数组down和up,如果取了一个数,就在上面做标记。比如说当6号位被取之后,那么9号位、3号位、4号位就不能取了,其中, u p i , d o w n i up_i,down_i upi,downi表示第 i i i列可行的区间即可。每次枚举的时候就可以判定,如果可以,就更新,如果不可以就直接跳过。
这道题目需要很多的细节,所以,经过反复调试,终于,推出了
A C A C A C AC_{AC}^{AC} ACACACACACACACACAC代码
#include
#include
#include
#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;
}