题目描述
输入描述:
输出描述:
示例1
输入
2
5
1 3 1 4
5 2 0 170
1
0 0 0 1
1000000000 1000000006 1000000006 1000000007
输出
26717147
10000019
说明
题目大意
给定
,则有数列
,其中
。有数列
和
,表示对
进行操作。
对于任意的
,操作就是把数列
中第
个修改成
。
现在顺序进行操作,得到
个数列(包括原数列),求将这些序列根据字典序从小到大排序后的名次的哈希值和是多少(名次从
开始)。
注:输入给出的是一些数字,为了避免输入耗时,根据题目中的描述生成
和
。
分析
接下来深解题意。
如图,
和
都已经生成,第一行为原数列,不操作。
那么根据名次
可以得到答案:
分治
首先看到这道题,肯定考虑暴力排序求解。但是很快发现复杂度不对,所以开始想一个棒棒的思路。
首先,我们从数列的最高位开始比较(这不就是字典序的比较顺序吗),当我们第一次发现不一样时,如下图。
从中间不一样的地方分开,发现下面的总是小于上面的数列。再对于每一部分分开考虑,如下图。
再找到第一个不一样的地方,发现总是可以用这种方法快速地进行排序。然后与所学的算法联系起来,这不就是分治吗?
但是还是有问题,如果把这个数列一个一个构造出来,和暴力没有区别。所以我们肯定是不能考虑这样做的。因此,只能从 和 下手。
因此,我们把
和
也一起分开试试。
观察这个图……但是还是看不出有什么规律。但是我们稍加分析就发现,操作0 0是没有意义的还有如果没有操作也是无法正常思考的,因此不妨我们设一个极大值inf。
于是我们惊奇地发现,每次可以根据
的最小值,然后据此上下分开,把较小的那边的名次从0开始,较大的那边从2开始。然后对于每段区间,再查找最小值,分开,名次偏移,再分,再偏移……直到只有一个为止,此时,我们只需要对于
进行操作判断即可。如图,
接下来就是考虑那边名次大。此时就要考虑每次操作之后字典序大小的变化。显然,每次操作,如果 时,字典序是变大的,此时下方的字典序就大于上面。反之亦然。
于是就有大体的分治的思路:找到 最小的位置,然后上下分开考虑,如果 ,则下方的名次偏移量较大,上方较小。
RMQ
上述思路中需要快速求出最小值。你可以选择许许多多的求最值的方法,线段树,树状数组, , ……但是这些都被出题人卡掉了(这是我T了百遍的ST代码)。因此我们需要一个构建是 的数据结构——笛卡尔树。
笛卡尔树的工作原理是这样的。
这里大概略写:用单调栈维护一条链,如果遍历到一个量违背了单调,则将元素弹出直到不违背。然后将该元素放到左子树上。
WAWA
注意dfs里面的信息传递,非常容易WA掉,还有一定用笛卡尔树!!!
代码
#include<bits/stdc++.h>
#define ll long long
#define inf 1<<30
using namespace std;
const int MAXN=2e6+10;
const int MOD=1e9+7;
int st[MAXN],ls[MAXN],rs[MAXN];
ll p[MAXN],d[MAXN];
int r[MAXN];
ll sd,a,b,mod,ans,M;
int n;
void init(int n){
st[0]=0;
for(int i=0;i<n;i++){
int k=st[0];
while(k>0&&p[st[k]]>p[i]) k--;
if(k) rs[st[k]]=i;
if(k<st[0]) ls[i]=st[k+1];
st[++k]=i; st[0]=k;
}
}//笛卡尔树
void dfs(int k,int left,int right,int rnk){
if(p[k]==inf||left>=right){for(int i=left;i<=right;i++) r[i]=rnk+(i-left);return;}//边界直接标记名次
dfs(ls[k],left,k,rnk+(p[k]%10>d[k])*(right-k));
dfs(rs[k],k+1,right,rnk+(p[k]%10<d[k])*(k-left+1));//注意rnk的转移
}
int main()
{
int t;scanf("%d",&t);
while(t--){
scanf("%d",&n);
scanf("%lld%lld%lld%lld",&sd,&a,&b,&mod);
for(int i=0;i<n;i++) p[i]=i;
for(int i=1;i<n;i++) swap(p[sd%(i+1)],p[i]),sd=(sd*a%mod+b)%mod;
scanf("%lld%lld%lld%lld",&sd,&a,&b,&mod);
for(int i=0;i<n;i++) d[i]=sd%10,sd=(sd*a%mod+b)%mod;
for(int i=0;i<n;i++) if(p[i]%10==d[i]) p[i]=inf;//如果无意义设为极大值
init(n);//建树
dfs(st[1],0,n,0);//dfs跑分治
ans=0;M=1;
for(int i=0;i<=n;i++) ans=(ans+((r[i])*M)%MOD)%MOD,M=(M*10000019)%MOD;//按题意哈希
printf("%lld\n",ans);
}
}
END
第一次学到笛卡尔树,%了%了。不过笛卡尔树的查询其实有点沙雕,只是在这道题里非常的吻合。而且OI也不会来卡这种东西,所以以后还是用ST……
嗯LaTeX真好用,博文漂亮多了。