bzoj 2081 Beads

探讨如何通过算法确定最佳切割长度,以获得最多不同的子串,解决Zxl的项链制作问题。使用字符串hash技术,考虑子串可反转特性,实现高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

Zxl有一次决定制造一条项链,她以非常便宜的价格买了一长条鲜艳的珊瑚珠子,她现在也有一个机器,能把这条珠子切成很多块(子串),每块有k(k>0)个珠子,如果这条珠子的长度不是k的倍数,最后一块小于k的就不要拉(nc真浪费),保证珠子的长度为正整数。 Zxl喜欢多样的项链,为她应该怎样选择数字k来尽可能得到更多的不同的子串感到好奇,子串都是可以反转的,换句话说,子串(1,2,3)和(3,2,1)是一样的。写一个程序,为Zxl决定最适合的k从而获得最多不同的子串。 例如:这一串珠子是: (1,1,1,2,2,2,3,3,3,1,2,3,3,1,2,2,1,3,3,2,1), k=1的时候,我们得到3个不同的子串: (1),(2),(3) k=2的时候,我们得到6个不同的子串: (1,1),(1,2),(2,2),(3,3),(3,1),(2,3) k=3的时候,我们得到5个不同的子串: (1,1,1),(2,2,2),(3,3,3),(1,2,3),(3,1,2) k=4的时候,我们得到5个不同的子串: (1,1,1,2),(2,2,3,3),(3,1,2,3),(3,1,2,2),(1,3,3,2)

Input

共有两行,第一行一个整数n代表珠子的长度,(n<=200000),第二行是由空格分开的颜色ai(1<=ai<=n)

Output

也有两行,第一行两个整数,第一个整数代表能获得的最大不同的子串个数,第二个整数代表能获得最大值的k的个数,第二行输出所有的k(中间有空格)。

Sample Input

21
1 1 1 2 2 2 3 3 3 1 2 3 3 1 2 2 1 3 3 2 1

Sample Output

6 1
2

解题思路

这个题是一个字符串各不同长度子串中不重复的个数的问题,
可以使用字符串hash,暴力枚举算出每种长度子串的hash,找出不重复的个数
特殊之处,项链可以反转,因此(1,2,3)和(3,2,1)认为是一样的,因此需要计算每个子串正序的hash和反序的hash,并把这两个hash值乘起来作为最终的hash值,这样(1,2,3)和(3,2,1)就认为是一样的了

借鉴冬令营清华尹昊萱的代码进行分析

需要的数据结构

两个存放正序hash和反序hash的数据:

#define LL unsigned long long
const int maxn=200010;
LL h[maxn],g[maxn]	//h是正序的,g是反序的

计算字符串hash时,依据的公式是:
hash[i] = ( hash[i−1]) ∗ p + id(s[i] ) % mod
这里要把字符串当初一个P进制数处理,p和mod应为质数,且p<mod
一般p可取13331。
对于mod 可以将结果存放在一个unsigned long long中
另外,还需要一个数组pow[ ]存放P进制数每一位的权值来提高效率

const int maxn=200010,p=13331;
LL pow[maxn];

每一个子串的hash值存入一个set中:

#include<set>
set<LL> s;
代码分析

首先读入整个字符串,放在数组a[]中,同时通过两个n次循环,计算正序和反序的每一个hash值:

    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);			//读入整个字符串
    for (int i=1;i<=n;i++) h[i]=h[i-1]*p+a[i];			//计算每一个正序的hash值,下标从1开始
    for (int i=1;i<=n;i++) g[i]=g[i-1]*p+a[n-i+1];			//计算每一个反序的hash值,下标从1开始

准备好P进制的每一位权值,放在pow数组中:

    pow[0]=1;
    for (int i=1;i<=n;i++) pow[i]=pow[i-1]*p;

j接下来,要做一个两重循环:
外层循环i,实际上是控制子串的长度,即每次切割项链的长度,第一次是1,每次切长度=1的子串,第二次i=2,每次切长度=2的子串,…最后一次i=n,切长度为n的子串,就是整个字符串。

内层循环j,控制每次切的子串的区间,包括正序的和逆序的。当i=1时,每次切长度为1个字符的子串,j=1时,正序切的是s1,反序切的是sn;j=2时,正序切的s2,反序切的s(n-1)…
当i=2时,每次切长度为2个字符的子串,j=1时,切的是s1s2,sns(n-1);j=3时,切的是s2s3,s(n-2)s(n-3)。。。

对于不同的子串,通过以下公式计算其hash值:
hash[sl…sr] = hash[r] - hash[l-1] * p^( r-l+1)^

外层循环i子串长度内层循环j正序子串正序hash值反序子串反序hash值
i=11j=1s1x[1…1]=h[1]-h[0]*pow[1]s1g[n…n]=g[n]-g[n-1]pow[1]
i=11j=2s2x[2…2]=h[2]-h[1]*pow[1]s2g[n-1…n-1]=g[n-1]-g[n-2]pow[1]
i=11j=3s3x[3…3]=h[3]-h[2]*pow[1]s3g[n-2…n-2]=g[n-2]-g[n-3]pow[1]
i=22j=1s1s2x[1…2]=h[2]-h[0]*pow[2]s2s1g[n…n-1]=g[n]-g[n-2]pow[2]
i=22j=3s3s4x[3…4]=h[4]-h[2]*pow[2]s4s3g[n-2…n-3]=g[n-2]-g[n-4]pow[2]
i=33j=1s1s2s3x[1…3]=h[3]-h[0]*pow[3]s3s2s1g[n…n-2]=g[n]-g[n-3]pow[3]
i=33j=4s4s5s6x[4…6]=h[6]-h[3]*pow[3]s6s5s4g[n-3…n-5]=g[n-3]-g[n-6]pow[3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值