HDU6988 Display Substring

传送门


这就是一个板儿题,结果好几种智障操作白给了。


首先用一种后缀数据结构求出本质不同的子串个数,再二分答案。

(我比赛的时候想复杂了,将第\(k\)小转换成了第\(k\)大,但本质上是一样的)

对于当前二分值\(mid\),我们要找所有权值大于等于\(mid\)的子串个数,那么对于SAM上的每一个点,可以要么选这个节点代表的所有子串,要么选择一部分子串。那么再进行一遍二分就行了。

时间复杂度\(O(n\log^2n)\).

#include<cstdio> 
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<assert.h>
using namespace std;
#define space putchar(' ')
#define enter puts("")
#define Mem(a, x) memset(a, x, sizeof(a))
#define forE(i, x, y) for(int i = head[x], y; ~i && ((y = e[i].to) >= 0); i = e[i].nxt)
#define In 
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int maxs = 27;
const int maxn = 8e5 + 5;
In ll read()
{
	ll ans = 0;
	char ch = getchar(), las = ' ';
	while(!isdigit(ch)) las = ch, ch = getchar();
	while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
	if(las == '-') ans = -ans;
	return ans;
}
In void write(ll x)
{
	if(x < 0) putchar('-'), x = -x;
	if(x >= 10) write(x / 10);
	putchar(x % 10 + '0');
}

char s[maxn];
int n, c[maxs], sum[maxn];
ll K;

struct Sam
{
	int tra[maxn << 1][maxs], link[maxn << 1], len[maxn << 1], endp[maxn << 1], cnt, las;
	In void init() {link[cnt = las = 0] = -1; Mem(tra[0], 0);}
	In void insert(int c, int id)
	{
		int now = ++cnt, p = las; Mem(tra[now], 0);
		len[now] = len[p] + 1, endp[now] = id;
		while(~p && !tra[p][c]) tra[p][c] = now, p = link[p];
		if(p == -1) link[now] = 0;
		else
		{
			int q = tra[p][c];
			if(len[q] == len[p] + 1) link[now] = q;
			else
			{
				int clo = ++cnt;
				memcpy(tra[clo], tra[q], sizeof(tra[q]));
				len[clo] = len[p] + 1, endp[clo] = endp[q];
				link[clo] = link[q]; link[q] = link[now] = clo;
				while(~p && tra[p][c] == q) tra[p][c] = clo, p = link[p];
			}
		}
		las = now;
	}
	int buc[maxn << 1], pos[maxn << 1];
	In ll solve()
	{
		ll ret = 0;
		for(int i = 1; i <= cnt; ++i) ret += len[i] - len[link[i]];
		return ret;
	}
	In int ggSum(int L, int R)
	{
		if(L == 0) return sum[R];
		else return sum[R] - sum[L - 1];
	}
	In int gSum(int now) 
	{
		return ggSum(endp[now] - len[now] + 1, endp[now]);
	}
	In ll calc(int x)
	{
		ll ret = 0;
		for(int i = 1; i <= cnt; ++i)
		{
			if(gSum(i) >= x)
			{
				ll L = len[link[i]] + 1, R = len[i];
				while(L < R)
				{
					int mid = (L + R) >> 1;
					if(ggSum(endp[i] - mid + 1, endp[i]) >= x) R = mid;
					else L = mid + 1;
				}			
				ret += len[i] - L + 1;
			}			
		}
		return ret;
	}
}S;

In int solve()
{
	S.init();
	for(int i = 0; i < n; ++i) S.insert(s[i] - 'a', i);
	for(int i = 0; i < n; ++i)
	{
		if(i) sum[i] = sum[i - 1] + c[s[i] - 'a'];
		else sum[i] = c[s[i] - 'a'];
	}
	ll Sum = S.solve();
	K = Sum - K + 1;
	if(K < 1) return -1;
	int L = 0, R = sum[n - 1];
	while(L < R)
	{
		int mid = (L + R + 1) >> 1;
		if(S.calc(mid) >= K) L = mid;
		else R = mid - 1;
	}
	return L;
}

int main()
{
	int T = read();
	while(T--)
	{
		n = read(), K = read();
		scanf("%s", s);
		for(int i = 0; i < 26; ++i) c[i] = read();
		write(solve()), enter;
	}
	return 0;
}

比赛的时候还建出了后缀树,然后进行树上差分,麻烦的很。

posted @ 2021-07-29 20:32  mrclr  阅读(92)  评论(0编辑  收藏  举报