题解 DTOJ #1585.Beads

欢迎访问 My Luogu Space

【题目描述】

Zxl有一次决定制造一条项链,她以非常便宜的价格买了一长条鲜艳的珊瑚珠子,她现在也有一个机器,能把这条珠子切成很多块(子串),每块有 k ( k > 0 ) k(k>0) k(k>0)个珠子,如果这条珠子的长度不是 k k k 的倍数,最后一块小于k的就不要拉(nc真浪费),保证珠子的长度为正整数。

Zxl喜欢多样的项链,为她应该怎样选择数字 k k k 来尽可能得到更多的不同的子串感到好奇,子串都是可以反转的,换句话说,子串 ( 1 , 2 , 3 ) (1, 2, 3) (1,2,3) ( 3 , 2 , 1 ) (3, 2, 1) (3,2,1) 是一样的。

写一个程序,为Zxl决定最适合的 k k k 从而获得最多不同的子串。

例如:

这一串珠子是: ( 1 , 1 , 1 , 2 , 2 , 2 , 3 , 3 , 3 , 1 , 2 , 3 , 3 , 1 , 2 , 2 , 1 , 3 , 3 , 2 , 1 ) (1,1,1,2,2,2,3,3,3,1,2,3,3,1,2,2,1,3,3,2,1) (1,1,1,2,2,2,3,3,3,1,2,3,3,1,2,2,1,3,3,2,1)

k = 1 k=1 k=1 的时候,我们得到 3 3 3 个不同的子串: ( 1 ) , ( 2 ) , ( 3 ) (1),(2),(3) (1),(2),(3)

k = 2 k=2 k=2 的时候,我们得到 6 6 6 个不同的子串: ( 1 , 1 ) , ( 1 , 2 ) , ( 2 , 2 ) , ( 3 , 3 ) , ( 3 , 1 ) , ( 2 , 3 ) (1,1),(1,2),(2,2),(3,3),(3,1),(2,3) (1,1),(1,2),(2,2),(3,3),(3,1),(2,3)
k = 3 k=3 k=3 的时候,我们得到 5 5 5 个不同的子串: ( 1 , 1 , 1 ) , ( 2 , 2 , 2 ) , ( 3 , 3 , 3 ) , ( 1 , 2 , 3 ) , ( 3 , 1 , 2 ) (1,1,1),(2,2,2),(3,3,3),(1,2,3),(3,1,2) (1,1,1),(2,2,2),(3,3,3),(1,2,3),(3,1,2)

k = 4 k=4 k=4 的时候,我们得到 5 5 5 个不同的子串: ( 1 , 1 , 1 , 2 ) , ( 2 , 2 , 3 , 3 ) , ( 3 , 1 , 2 , 3 ) , ( 3 , 1 , 2 , 2 ) , ( 1 , 3 , 3 , 2 ) (1,1,1,2),(2,2,3,3),(3,1,2,3),(3,1,2,2),(1,3,3,2) (1,1,1,2),(2,2,3,3),(3,1,2,3),(3,1,2,2),(1,3,3,2)

【 输入输出格式】

输入格式:

共有两行,第一行一个整数 n n n 代表珠子的长度,第二行是由空格分开的颜色。

输出格式:

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

【输入输出样例】

输入样例:

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

输出样例:

6 1
2

【提示】

【数据范围】

a i ( 1 &lt; = a i &lt; = n , n &lt; = 200005 ) ai(1&lt;=ai&lt;=n,n&lt;=200005) ai1<=ai<=nn<=200005


【标签】

哈希。


【分析】

枚举k,通过哈希来快速对字符串判重。

基本想法:

从小到大枚举 k k k ,再对于每个 k k k 进行不同的字符串的统计。效率 O ( k n ) O(kn) O(kn)

改进:

对于不同的字符串判断重复,我们可以讲字符串哈希一下,分别记录一个前缀哈希和一个后缀哈希。通过两个前缀和相减的方法就可以快速取出对应段的字符串的哈希值(注意!由于是字符串,每次添加的新字符是在原来字符串的后端,如果要能达到哈希值相减,需要先将位数较小的哈希值乘上两个哈希值之间的进制数位数之差,也就是差了几个字符。同代码中的 B B B 数组。),再通过STL中的map就可以完成快速的判重。

复杂度降为 O ( k l o g ( k ) ) O(klog(k)) O(klog(k))


【代码】

[C++]

#include <bits/stdc++.h>
using namespace std;
const int HASH = 19260817;  //哈希值自取

int n, sum, Max, S[200055], Hash1[200055], Hash2[200055], B[200055];
map<int,bool> M;
vector<int> Ans;

int main(){
	scanf("%d", &n);
	for(int i=1; i<=n; ++i) scanf("%d", &S[i]);	
	int temp1, temp2; B[0] = 1;
	for(int i=1; i<=n; ++i) Hash1[i] = Hash1[i-1]*HASH+S[i], B[i] = B[i-1]*HASH;//处理对应位数的进制数
	for(int i=n; i; --i) Hash2[i] = Hash2[i+1]*HASH+S[i];
	for(int k=1; k<=n; ++k){
		if(n/k < Max) break;  //小优化
		M.clear(); sum = 0;
		for(int i=1; i+k-1<=n; i+=k){
			temp1 = Hash1[i+k-1]-Hash1[i-1]*B[k];  //正着读的字符串
			temp2 = Hash2[i]-Hash2[i+k]*B[k];  //反着读的字符串
			if(!M[temp1]) sum++, M[temp1] = 1, M[temp2] = 1;
		}
		if(sum == Max) Ans.push_back(k);
		else if(sum > Max) Max = sum, Ans.clear(), Ans.push_back(k);
	}
	printf("%d %d\n", Max, Ans.size());
	for(int i=0,end=Ans.size(); i<end; ++i) printf("%d ", Ans[i]);
	return 0;
}


【补充】

随便写。


Over

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值