2020牛客暑期多校训练营第三场Sort the Strings Revision

Sort the Strings Revision

原题请看这里

题目描述:

有n+1个长度为n的字符串---- S 1 S_1 S1, S 2 S_2 S2,…, S n S_n Sn。 字符串中的字符从0到n-1(从左到右)编号。字符 S i S_i Si i i i模10的数字。例如,如果n=5,则S为“01234”,如果n=14,则S为“ 01234567890123”。给定一个0到n-1的排列: p [ 0 ] p[0] p[0], p [ 1 ] p [1] p[1],…, p [ n − 1 ] p[n-1] p[n1]和长度为n的数字序列: d [ 0 ] d[0] d[0], d [ 1 ] d [1] d[1],…, d [ n − 1 ] d[n-1] d[n1] S i + 1 S_{i+1} Si+1可以通过用数字 d [ i ] d[i] d[i]代替 S i S_i Si的字符 p [ i ] p[i] p[i]来获得。 请注意,如果 d [ i ] d[i] d[i] S i S_i Si的字符 p [ i ] p[i] p[i]相同,则S将与 S i S_i Si相同.现在我们要对这n+1个字符串进行排序。 当且仅当 S i S_i Si在字典上小于 S j S_j Sj S i S_i Si等于 S j S_j Sj并且 i i i< j j j时,才应将索引为i的字符串放在索引为j的字符串的左侧。让 r i r_i ri为字符串 S i S_i Si从左开始的新位置, 也就是说, S r i S_{ri} Sri是排序后从左起第 i i i个字符串。(位置是从0开始的数字)例如,如果n=5,p=[4,3,0,1,2]和d=[5,0,0,0,0],则 S 0 S_0 S0=“ 01234”, S 1 S_1 S1=“01235”, S 2 S_2 S2=“01205”, S 3 S_3 S3=“01205”, S 4 S_4 S4=“00205”, S 5 S_5 S5=“00005”。因此 r 0 r_0 r0=4, r 1 r_1 r1=5, r 2 r_2 r2=2, r 3 r_3 r3=3, r 4 r_4 r4=1和 r 5 r_5 r5=0。请求出从0到n的所有的 r i r_i ri

输入描述:

第一行包含一个整数t(1≤t≤10^ 3)表示测试用例的数量。每个测试的第一行包含一个正整数n(1≤n≤2×10^ 6)。每个测试的第二行包含四个整数 p s e e d p_{seed} pseed p a p_a pa p b p_b pb p m o d p_{mod} pmod(0≤ p s e e d p_{seed} pseed p a p_a pa p b p_b pb < p m o d p_{mod} pmod≤10^ 9+7)。 使用以下伪代码生成p。
s e e d seed seed = = = p s e e d p_{seed} pseed
f o r for for i i i = = = 0 0 0 t o to to n − 1 n-1 n1 : : : p [ i ] p[i] p[i]= i i i
f o r for for i i i = = = 1 1 1 t o to to n − 1 n-1 n1
{
s w a p swap swap t h e the the v a l u e value value o f of of p [ p[ p[ s e e d seed seed m o d u l o modulo modulo ( i + 1 ) (i + 1) (i+1) ] ] ]
p [ p[ p[ s e e d seed seed m o d u l o modulo modulo ( i + 1 ) (i+1) (i+1) ] ] ] a n d and and p [ i ] p[i] p[i]
s e e d seed seed = = = ( ( ( s e e d seed seed ∗ * p a p_a pa + + + p b p_b pb ) ) ) m o d u l o modulo modulo p m o d p_{mod} pmod
}
每个测试的第三行包含四个整数 d s e e d d_ {seed} dseed d a d_a da d b d_b db d m o d d_ {mod} dmod(0 ≤ \le d s e e d d_ {seed} dseed d a d_a da d b d_b db < d m o d d_ {mod} dmod ≤ \le 10^9+7)。 使用以下伪代码生成d。
s e e d seed seed = = = d s e e d d_{seed} dseed
f o r for for i i i = = = 0 0 0 t o to to n − 1 n-1 n1 : : :
d [ i ] d[i] d[i] = = = s e e d seed seed m o d u l o modulo modulo 10 10 10
s e e d seed seed = = = ( ( ( s e e d seed seed ∗ * d a d_a da + + + d b d_b db ) ) ) m o d u l o modulo modulo d m o d d_{mod} dmod
测试用例中n的总和不超过10 ^ 7

输出描述:

对于每个测试,您不需要输出 r i r_i ri的整个序列。 相反,您应该输出一个非负整数
( ( ( i = 0 ∑ n i=0∑n i=0n( r i r_i ri ∗ ∗ 1000001 9 i 10000019^i 10000019i ) ) )) )) m o d mod mod 1000000007 1000000007 1000000007 . . .

样例输入:

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

样例输出:

26717147
10000019

思路:

二分+笛卡尔树 数据卡ST表T了好几遍emmmm…
通过观察 p i p_i pi的最小值可以发现,每次可以通过 p i p_i pi的最小值来将数列划分为两部分,在 1 1 1 ~ p i p_i pi之间和 p i + 1 p_{i+1} pi+1 ~ p n p_n pn之间重复上述过程,在 d i d_i di> p i p_i pi时字典序变大…这不就是二分吗?
所以这道题可以用二分来解:每次找到最小的 p i p_i pi,将数列分成两部分,再继续递归,直到只有一个为止,最后比较 d i d_i di p i p_i pi的大小。
那么问题来了,如何才能快速求出区间内 p i p_i pi的最小值呐?这就需要引进一个知识——笛卡尔树(ST表会T)
笛卡尔树的原理大概是这样的:
用单调栈来维护一个链表,如果违背单调栈,那就放在左子树上(这里就不详细讲了,如有需要请看这里

AC Code:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int HASH=1e7+19;
const int MAXN=2e6+5;
const ll MAX=INT_MAX+1e8;
const ll mod=1e9+7;
ll Pseed,Pa,Pb,Pmod,P[MAXN],Dseed,Da,Db,Dmod,D[MAXN];
ll st[MAXN],ans,Rank[MAXN],has,l[MAXN],r[MAXN];
int t,n,val;
void cartesian()
{
    int tmp=0,x;
    for(int i=0;i<n;i++)
    {
        x=tmp;
        while(x>0&&P[st[x]]>P[i]) x--;
        if(x) r[st[x]]=i;
        if(x<tmp) l[i]=st[x+1];
        st[++x]=i;
        tmp=x;
    }
}//笛卡尔树
void dfs(int left,int right,int minn)
{
    if(left>right) return;
    if(left==right){Rank[left]=val++;return;}
    if(P[minn]==MAX)
    {
        for(int i=left;i<=right;i++)
            Rank[i]=val++;
        return;
    }
    if(P[minn]%10>D[minn]) dfs(minn+1,right,r[minn]),dfs(left,minn,l[minn]);
    else dfs(left,minn,l[minn]),dfs(minn+1,right,r[minn]);
}//二分
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        has=1;
        val=0;
        ans=0;
        scanf("%d",&n);
        scanf("%lld%lld%lld%lld",&Pseed,&Pa,&Pb,&Pmod);
        scanf("%lld%lld%lld%lld",&Dseed,&Da,&Db,&Dmod);
        for(int i=0;i<n;i++)
        {
            P[i]=i;
            D[i]=Dseed%10;
            Dseed=(Dseed*Da+Db)%Dmod;
        }
        for(int i=1;i<n;i++)
        {
            swap(P[i],P[Pseed%(i+1)]);
            Pseed=(Pseed*Pa+Pb)%Pmod;
        }
        for(int i=0;i<n;i++)
            if(P[i]%10==D[i]) P[i]=MAX;//无用数据
        cartesian();
        dfs(0,n,st[1]);
        for(int i=0;i<=n;i++)
        {
            ans+=(ll)(Rank[i]*has)%mod;
            ans%=mod;
            has*=HASH;
            has%=mod;
        }
        printf("%lld\n",ans%mod);
    }
}

本文参考博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值