[2018.03.14 T3] 圈圈(cyclic)

15 篇文章 0 订阅
14 篇文章 0 订阅

团队题库链接:https://www.luogu.org/problemnew/show/T24367

圈圈(cyclic)

【题目描述】

shy 有一个队列 a[1], a[2],…,a[n]。现在我们不停地把头上的元素放到尾巴上。在这过程中我们会得到 n 个不同的队列,每个队列都是 a[k],a[k+1],…,a[n],a[1],…,a[k-1] 的形式。在这些队列中,我们可以找到字典序最小的。

shy 无聊的时候会给队列的每个元素加一玩。但是为了使得游戏不这么无聊,shy 加一以后会给每个元素模 m,这样子字典序最小的序列就会变了,生活就变得有趣。

很显然这样子加 m 次以后,序列会变成原来的样子。所以现在 shy 想知道,在他没有加一前,加一时,加二时,….,加 m-1 时字典序最小的序列的第 k(和上面的 k 没有关系)个元素分别是几。

【输入】

第一行三个整数 n,m,k 表示序列长度,取模的数和要求的序列的第几个元素。

接下来一行 n 个整数表示初始序列。

【输出】

m 个整数表示答案。

【输入样例】

5 6 3
1 2 1
2 3

【输出样例】

1
2
3
5
5
0

【提示】
【数据规模】

对于 30%的数据,1≤n,m≤100;

对于 100%的数据,1≤n,m≤50000, 1≤k≤n, 0≤a[i]

题解

暴力WA了,实际得分0。。。

翻了翻网上的题解说要用lcp,以为要用后缀自动机,吓得弃疗。

然而事实上只需要哈希+二分即可,不知道lcp这个东西也能A。。。orz,经大佬讲解以后豁然开朗然而事实并没有

首先,当没有数变成零时,字典序最小的序列是不会变的。由于每个数都比m小,所以每个数有且仅有一次会变成零,当且仅当有数变零时,字典序最小的序列会改变,所以我们只需记录在加到几时,有哪些位置的数字变成了零,在那一层我们只需要以那些节点为起点,二分长度比较两个序列找出字典序最小的串就可以求出每层的答案。

为了快速提取出两个串进行比较,我们需要字符串哈希求出前缀和,提取时两个前缀和相减即可。

以基数x为例,abcde中的e的哈希值前缀和 ax4+bx3+cx2+dx+e a x 4 + b x 3 + c x 2 + d x + e ,b的哈希值前缀和为 ax+b a x + b ,当我们想要提取出字符串cde时,只需用b点前缀和,乘以基数的所求字符串长度次方,就变成了 ax4+b3 a x 4 + b 3 ,再相减我们就成功提取出了字符串哈希值就可以进行比较了。

博主依然用的自然溢出(懒得去摸),再加一个双哈希(RP好的同学可以尝试单哈希)。

代码
#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int M=100005;
int a[M],ans[M],n,m,k,r;
ull base[2],key[2][M],h[2][M];
vector<int>g[M];
char c;
int read()
{
    r=0;
    c=getchar();
    while(!isdigit(c))c=getchar();
    while(isdigit(c))r=(r<<1)+(r<<3)+c-'0',c=getchar();
    return r;
}
void out(int x)
{
    if(x>9)out(x/10);
    putchar(x%10+'0');
}
void getkey()
{
    base[0]=19260817;
    base[1]=19491001;
    key[0][0]=key[1][0]=1;
    for(int i=1;i<M;++i)
    for(int p=0;p<2;++p)
    key[p][i]=key[p][i-1]*base[p];
}
void in()
{
//  scanf("%d%d%d",&n,&m,&k);
    n=read();m=read();k=read();
    k--;
    for(int i=1;i<=n;++i)
    {
    //  scanf("%d",&a[i]);
        a[i]=read();
        a[i+n]=a[i];
        g[(m-a[i])%m].push_back(i);
    }
}
bool check(int x,int y,int len)
{
    if(!len)return 1;
    if(x>y)swap(x,y);
    ull ha,hb,ah,bh;
    for(int p=0;p<2;++p)
    {
        ha=hb=0;
        if(x>0)ha=h[p][x-1]*key[p][len];
        if(y>0)hb=h[p][y-1]*key[p][len];
        ah=h[p][x+len-1]-ha;
        bh=h[p][y+len-1]-hb;
        if(ah!=bh)return 0;
    }
    return 1;
}
bool cmp(int x,int y,int t)
{
    int le=0,ri=n,len=-1,mid;
    while(le<=ri)
    {
        mid=(le+ri)>>1;
        if(check(x,y,mid)) len=mid,le=mid+1;
        else ri=mid-1;
    }
    if(len==n)return 0;
    if((a[x+len]+t)%m<(a[y+len]+t)%m)return 1;
    return 0;
}
void ac()
{
    int hh=n<<1;
    h[0][0]=h[1][0]=1;
    for(int i=1;i<=hh;++i)
    for(int p=0;p<2;++p)
    h[p][i]=h[p][i-1]*base[p]+a[i];
    int p=1;
    for(int i=2;i<=n;++i)
    if(cmp(i,p,0))p=i;
    ans[0]=a[p+k];
    for(int i=1;i<m;++i)
    {
        if(!g[i].size())ans[i]=ans[i-1]+1;
        else
        {
            p=g[i][0];
            for(int j=g[i].size()-1;j>=1;--j)
            if(cmp(g[i][j],p,i))p=g[i][j];
            ans[i]=(a[p+k]+i)%m;
        }
    }
    for(int i=0;i<m;++i)
//  printf("%d\n",ans[i]);
    out(ans[i]),putchar(10);
}
int main()
{
    getkey();in();ac();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ShadyPi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值