2020牛客暑期多校训练营Sort the Strings Revision(分治,RMQ(笛卡尔树))

Sort the Strings Revision

题目描述

在这里插入图片描述

输入描述:

在这里插入图片描述

输出描述:

在这里插入图片描述

示例1

输入

2
5
1 3 1 4
5 2 0 170
1
0 0 0 1
1000000000 1000000006 1000000006 1000000007

输出

26717147
10000019

说明

在这里插入图片描述

题目大意

给定 n n ,则有数列 0 , 1 , . . . 0,1,... ,其中 n i = i m o d 10 n_i=i\,mod \,10 。有数列 p p d d ,表示对 n n 进行操作。
对于任意的 i i ,操作就是把数列 0 , 1 , , n 0,1,\dots,n 中第 p i p_i 个修改成 d i d_i
现在顺序进行操作,得到 n + 1 n+1 个数列(包括原数列),求将这些序列根据字典序从小到大排序后的名次的哈希值和是多少(名次从 0 0 开始)。
注:输入给出的是一些数字,为了避免输入耗时,根据题目中的描述生成 d d p p

分析

接下来深解题意。
在这里插入图片描述
如图, p p d d 都已经生成,第一行为原数列,不操作。
那么根据名次 4 , 5 , 2 , 3 , 1 , 0 4,5,2,3,1,0 可以得到答案:
i = 0 n ( r i 1000001 9 i ) m o d 1000000007 \qquad\mathop{\sum}\limits_{i=0}^n(r_i*10000019^i)\,mod\,1000000007
= ( 4 1000001 9 0 + 5 1000001 9 1 + + 0 1000001 9 5 ) m o d 1000000007 =(4*10000019^0+5*10000019^1+\dots+0*10000019^5)\,mod\,1000000007
= 26717147 =26717147

分治

首先看到这道题,肯定考虑暴力排序求解。但是很快发现复杂度不对,所以开始想一个棒棒的思路。

首先,我们从数列的最高位开始比较(这不就是字典序的比较顺序吗),当我们第一次发现不一样时,如下图。
在这里插入图片描述
从中间不一样的地方分开,发现下面的总是小于上面的数列。再对于每一部分分开考虑,如下图。
在这里插入图片描述
再找到第一个不一样的地方,发现总是可以用这种方法快速地进行排序。然后与所学的算法联系起来,这不就是分治吗?

但是还是有问题,如果把这个数列一个一个构造出来,和暴力没有区别。所以我们肯定是不能考虑这样做的。因此,只能从 p p b b 下手。

因此,我们把 p p b b 也一起分开试试。
在这里插入图片描述
观察这个图……但是还是看不出有什么规律。但是我们稍加分析就发现,操作0 0是没有意义的还有如果没有操作也是无法正常思考的,因此不妨我们设一个极大值inf。
在这里插入图片描述
于是我们惊奇地发现,每次可以根据 p p 的最小值,然后据此上下分开,把较小的那边的名次从0开始,较大的那边从2开始。然后对于每段区间,再查找最小值,分开,名次偏移,再分,再偏移……直到只有一个为止,此时,我们只需要对于 p p 进行操作判断即可。如图,
在这里插入图片描述

接下来就是考虑那边名次大。此时就要考虑每次操作之后字典序大小的变化。显然,每次操作,如果 d i > p i d_i>p_i 时,字典序是变大的,此时下方的字典序就大于上面。反之亦然。

于是就有大体的分治的思路:找到 p p 最小的位置,然后上下分开考虑,如果 d i > p i d_i>p_i ,则下方的名次偏移量较大,上方较小。

RMQ

上述思路中需要快速求出最小值。你可以选择许许多多的求最值的方法,线段树,树状数组, R M Q RMQ S T ST ……但是这些都被出题人卡掉了(是我T了百遍的ST代码)。因此我们需要一个构建是 O ( n ) O(n) 的数据结构——笛卡尔树

笛卡尔树的工作原理是这样的。
这里大概略写:用单调栈维护一条链,如果遍历到一个量违背了单调,则将元素弹出直到不违背。然后将该元素放到左子树上。

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真好用,博文漂亮多了

猜你喜欢

转载自blog.csdn.net/zhangchizc/article/details/107515512