4310: 跳蚤

4310: 跳蚤

Time Limit: 20 Sec   Memory Limit: 512 MB
Submit: 306   Solved: 146
[ Submit][ Status][ Discuss]

Description

很久很久以前,森林里住着一群跳蚤。一天,跳蚤国王得到了一个神秘的字符串,它想进行研究。
首先,他会把串分成不超过 k 个子串,然后对于每个子串 S,他会从S的所有子串中选择字典序最大的那一个,并在选出来的 k 个子串中选择字典序最大的那一个。他称其为“魔力串”。
现在他想找一个最优的分法让“魔力串”字典序最小。

Input

第一行一个整数 k。
接下来一个长度不超过 105 的字符串 S。

Output

输出一行,表示字典序最小的“魔力串”。

Sample Input

13
bcbcbacbbbbbabbacbcbacbbababaabbbaabacacbbbccaccbcaabcacbacbcabaacbccbbcbcbacccbcccbbcaacabacaaaaaba

Sample Output

cbc

HINT

S的长度<=100000

Source

[ Submit][ Status][ Discuss]

要求第k大串,,显然是要二分,先构建出后缀数组
然后,本质不同的子串个数是∑(n - sa[i] - height[i] + 1)
这样,每次二分一个子串,看能否构建出一个符合解。
拿出一个子串以后,对于原串,从后往前for一遍,每次求LCP,看最多能匹配多少位
分类讨论一下,尽可能少分段,,
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<stack>
#include<bitset>
#include<ext/pb_ds/priority_queue.hpp>
using namespace std;

const int maxn = 1E5 + 10;
const int T = 19;
typedef long long LL;

int n,k,l,r,height[maxn],rank[maxn],sa[maxn],t1[maxn]
	,t2[maxn],c[maxn],Ans[maxn][T],bin[maxn],len[maxn];
char s[maxn];

void Get_SA(int m)
{
	int *x = t1,*y = t2;
	for (int i = 1; i <= n; i++) ++c[x[i] = s[i]];
	for (int i = 2; i <= m; i++) c[i] += c[i-1];
	for (int i = n; i; i--) sa[c[x[i]]--] = i;
	for (int k = 1; k < n; k <<= 1)
	{
		int p = 0;
		for (int i = n; i > n - k; i--) y[++p] = i;
		for (int i = 1; i <= n; i++) if (sa[i] > k) y[++p] = sa[i] - k;
		for (int i = 1; i <= m; i++) c[i] = 0;
		for (int i = 1; i <= n; i++) ++c[x[y[i]]];
		for (int i = 2; i <= m; i++) c[i] += c[i-1];
		for (int i = n; i; i--) sa[c[x[y[i]]]--] = y[i];
		swap(x,y); p = 1; x[sa[1]] = 1;
		for (int i = 2; i <= n; i++)
			x[sa[i]] = y[sa[i]] == y[sa[i-1]] && y[sa[i]+k] == y[sa[i-1]+k] ? p : ++p;  
		if (p >= n) return; m = p;
	}
}

void Get_Height_And_Rank()
{
	int k = 0;
	for (int i = 1; i <= n; i++) rank[sa[i]] = i;
	for (int i = 1; i <= n; i++)
	{
		if (k) --k;
		int j = sa[rank[i] - 1];
		while (s[i+k] == s[j+k]) ++k;
		height[rank[i]] = k;
	}
}

void RMQ_Pre()
{
	for (int i = 1; i <= n; i++) 
	{
		bin[i] = (1<<bin[i-1]+1) < i ? bin[i-1]+1 : bin[i-1];
		Ans[i][0] = height[i]; len[i] = (1<<bin[i]);
	}
	for (int j = 1; j < T; j++)
		for (int i = 1; i <= n; i++)
		{
			int Nex = i + (1<<j-1); if (Nex > n) break;
			Ans[i][j] = min(Ans[i][j-1],Ans[Nex][j-1]);
		}
}

void Get(LL res)
{
	LL tot = 0;
	for (int i = 1; res; i++)
	{
		int now = n - sa[i] - height[i] + 1;
		if (res > now) res -= 1LL*now;
		else l = sa[i],r = sa[i] + height[i] + res - 1,res = 0;
	}
}

int RMQ(int x,int y)
{
	int k = y - x + 1;
	return min(Ans[x][bin[k]],Ans[y-len[k]+1][bin[k]]);
}

int LCP(int x,int y)
{
	if (x == y) return n - x + 1;
	x = rank[x]; y = rank[y]; 
	if (x > y) swap(x,y);
	return RMQ(x + 1,y);
}

bool cmp(int l1,int r1,int l2,int r2)
{
	int len1 = r1 - l1 + 1,len2 = r2 - l2 + 1,len = LCP(l1,l2);
	if (len >= len2 && len1 > len2) return 1;
	if (len >= len1 && len2 >= len1) return 0;
	if (len >= len1 && len >= len2) return len1 > len2;
	return s[l1 + len] > s[l2 + len];
}

bool Judge(LL now)
{
	int sum = 0,last = n; Get(now);
	for (int i = n; i; i--)
	{
		if (s[i] > s[l]) return 0;
		if (cmp(i,last,l,r)) ++sum,last = i;
		if (sum > k) return 0;
	}
	return sum + 1 <= k;
}

void Print(LL goal)
{
	Get(goal);
	for (int i = l; i <= r; i++) putchar(s[i] + 'a' - 1);
}

int main()
{
	#ifdef DMC
		freopen("DMC.txt","r",stdin);
	#endif
	
	cin >> k; scanf("%s",s + 1); n = strlen(s + 1); 
	for (int i = 1; i <= n; i++) s[i] = s[i] - 'a' + 1;
	Get_SA(26); Get_Height_And_Rank(); RMQ_Pre();
	
	LL L = 1,R = 0;
	for (int i = 1; i <= n; i++) R += 1LL*(n - sa[i] + 1 - height[i]);
	while (R - L > 1)
	{
		LL mid = (L + R) >> 1LL;
		if (Judge(mid)) R = mid;
		else L = mid;
	}
	Print(Judge(L) ? L : R);
	return 0;
}


一直想着如何用后缀自动机做,,,结果是还得掌握后缀数组= =

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值