【BZOJ3671】[NOI2014] 随机数生成器(听说这题卡常于是我就愉快地被卡了内存)
大致题意: 给你一种随机数生成方式,让你生成一个矩阵。然后求从\((1,1)\)到\((n,m)\)路径上的数从小到大排序后字典序最小的数列。
前言
我个人感觉这道题之所以要有个随机数生成器大概只是为了防止读入超时?
反正刚看见题目还以为这又是一道毒瘤的概率期望题。
一开始被卡了内存,后来发现我智障开了个完全没必要的数组,然后顺便把几个数组从\(int\)改为\(short\)就过了。
贪心
说实话这道题其实蛮水的。
考虑从小到大排序后字典序最小,那我们就从小到大枚举每一个数能不能选。
而一个数能选,就需要它上面的数都在它左边,它下面的数都在它右边。
这个“都”看看都烦,如果我们求个最值,就变成了它上面的数中最右边的在它左边,它下面的数最左边的在它右边。
然后我觉得我数据结构学傻了,碰到这种最值一眼就想搞个数据结构上来维护,差点把原本完全可以\(O(n)\)修改\(O(1)\)查询的东西搞成\(O(logn)\)修改\(O(logn)\)查询,使复杂度平白无故多个\(log\)。(而且还会被卡内存卡得更惨)
实际上,每次确定一个数会被选,我们就可以枚举每一行更新这一行上方最右边的数/这一行下方最左边的数。
由于只会选\(n+m-1\)个数,所以这样的复杂度是\(O(n^2)\)的。
具体实现详见代码。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 5000
#define Gmax(x,y) (x<(y)&&(x=(y)))
#define Gmin(x,y) (x>(y)&&(x=(y)))
#define swap(x,y) (x^y)&&(x^=y^=x^=y)
using namespace std;
int n,m,lim,p[N*N+5],ans[2*N+5];short r[N*N+5],c[N*N+5],Mn[N+5],Mx[N+5];
struct Rand//随机数生成器
{
int x,a,b,c,d;I void read() {scanf("%d%d%d%d%d",&x,&a,&b,&c,&d);}
I int GV() {return x=((1LL*a*x+b)%d*x+c)%d;}//按题意生成随机数
}R;
I void GetData()
{
RI i,j,Qt,x,y;R.read(),scanf("%d%d%d",&n,&m,&Qt),lim=n*m;
for(i=1;i<=lim;++i) p[i]=i;//初始从小到大
for(i=1;i<=lim;++i) x=R.GV()%i+1,swap(p[i],p[x]);//每次交换
W(Qt--) scanf("%d%d",&x,&y),swap(p[x],p[y]);//再进行Q次交换
for(i=1;i<=n;++i) for(j=1;j<=m;++j) r[p[(i-1)*m+j]]=i,c[p[(i-1)*m+j]]=j;//记下每个元素的行列
}
int main()
{
RI i,j,cnt=0;for(GetData(),i=1;i<=n;++i) Mn[i]=1,Mx[i]=m;
for(i=1;i<=lim;++i)//从小到大枚举每个元素能否填入
{
if(c[i]<Mn[r[i]]||c[i]>Mx[r[i]]) continue;if(ans[++cnt]=i,cnt==n+m-1) break;//如果不能填跳过,如果填满退出
for(j=1;j^r[i];++j) Gmin(Mx[j],c[i]);for(j=n;j^r[i];--j) Gmax(Mn[j],c[i]);//更新其他行
}
for(i=1;i<=cnt;++i) printf("%d%c",ans[i]," \n"[i==cnt]);return 0;//输出答案
}
待到再迷茫时回头望,所有脚印会发出光芒