给定一个长为n(3e5)的字符串,如果两个后缀
l
,
r
l,r
l,r的
l
c
p
lcp
lcp至少为
i
i
i,那么称它们是
i
相
似
i相似
i相似的。每个位置还有一个权值a(±1e9)。
现在对于每个
i
=
0
到
n
−
1
i=0到n-1
i=0到n−1,问有多少个
i
相
似
i相似
i相似的无序对
(
l
,
r
)
(l,r)
(l,r),并且输出最大的
a
l
∗
a
r
a_l*a_r
al∗ar
和lcp有关,自然想到后缀数组。跑一遍后缀数组求出height之后,这题就和字符串没什么关系了。。
考虑从大到小找答案,按height从大到小遍历id,每一个id的意义是id和id-1这两种酒是height[id]相似的。
也就是说,每个id的意义相当于把id-1和id这两种酒连起来了。
和并查集非常像,但每次合并之后需要知道新增了多少个无序对以及合并后新集合无序对的最大积。
易知新增无序对数等于两个集合的size之积。
为了求最大积,我们可以维护每个集合的最大值、最小值、最大积,然后对【原来的最大积】、【最大值之积】、【最小值之积】取max,就是新的最大积。
总结:
- 好多题的字符串只是一个壳。。。内部全是其它东西。
- 并查集的最大积可以通过维护最大值,最小值,历史最大积维护。
- 洛谷黑题第二道,独立做出的哟
/* LittleFall : Hello! */
#include <bits/stdc++.h>
using namespace std; using ll = long long; inline int read();
const int M = 300016, MOD = 1000000007;
char str[M];
namespace SA
{
/* 后缀数组 */
int sa[M], ra[M], height[M]; //后缀三数组,sa和ra下标从0开始,height下标从1开始
int t1[M], t2[M], c[M]; // 用于基数排序的三个辅助数组
void build(char *str, int n, int m) // 构造后缀三数组,字符串下标从0开始,n表示长度,m表示字符集大小
{
str[n] = 0;
n++;
int i, j, p, *x = t1, *y = t2;
for(i = 0; i < m; i++) c[i] = 0;
for(i = 0; i < n; i++) c[x[i]=str[i]]++;
for(i = 1; i < m; i++) c[i] += c[i-1];
for(i = n-1; i >= 0; i--) sa[--c[x[i]]] = i;
for(j = 1; j <= n; j<<=1)
{
p = 0;
for(i = n-j; i < n; i++) y[p++] = i;
for(i = 0; i < n; i++) if(sa[i] >= j) y[p++] = sa[i]-j;
for(i = 0; i < m; i++) c[i] = 0;
for(i = 0; i < n; i++) c[x[y[i]]]++;
for(i = 1; i < m; i++) c[i] += c[i-1];
for(i = n-1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i];
swap(x, y);
p = 1; x[sa[0]] = 0;
for(i = 1; i < n; i++)
x[sa[i]] = (y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+j]==y[sa[i]+j]) ? p-1 : p++;
if(p >= n) break;
m = p;
}
n--;
for(int i = 0; i <= n; i++) ra[sa[i]] = i;
for(int i=0, j=0, k=0; i < n; i++)
{
if(k) k--;
j = sa[ra[i]-1];
while(str[i+k]==str[j+k]) k++;
height[ra[i]] = k;
}
}
};
struct UFS
{
int fa[M], cnt[M]; //父亲(当父亲不是自己时失效),元素个数
ll mx[M], mi[M], mul_mx[M];
//集合中元素个数,最大值,最小值,最大对乘积
void init(int n, ll *pri)
{
for(int i=1; i<=n; ++i)
{
cnt[i] = 1; fa[i] = i;
mx[i] = mi[i] = pri[i];
mul_mx[i] = LLONG_MIN;
}
}
int find(int i)
{
return fa[i]==i ? i : fa[i] = find(fa[i]);
}
//合并集合,返回新增对数,最大对乘积
pair<ll,ll> join(int a, int b)
{
a = find(a), b = find(b);
ll res_cnt = 1ll*cnt[a]*cnt[b];
ll res_max = max(mul_mx[a], mul_mx[b]);
res_max = max(res_max, mx[a]*mx[b]);
res_max = max(res_max, mi[a]*mi[b]);
fa[b] = a;
mx[a] = max(mx[a], mx[b]);
mi[a] = min(mi[a], mi[b]);
cnt[a] += cnt[b];
mul_mx[a] = res_max;
return {res_cnt, res_max};
}
}ufs;
/*
每个id的意义在于, id和id-1这两种酒是v相似的。
也就是说,每个id实际上是把id和id-1这两种酒连起来了
我要把它们放到一个并查集里面去
cnt的增量等于,待合并集合的元素个数之积
max的更新,max可以取得哪些值呢
这个问题实际上是: 一个可以求最大乘积的并查集
初始时有n个数,每个数属于自己的集合,若干次操作:
选定x和y,把第x个数和第y个数所在的集合合并,
返回合并后集合中任意两个数最大的乘积(不能是一个数)
*/
ll pri[M]; using SA::height; //权值,height
ll ans_cnt[M], ans_max[M]; //答案计数,答案最大值
vector<int> ids[M]; //ids[i]表示height=i的所有位置
//带并查集
int main(void)
{
#ifdef _LITTLEFALL_
freopen("in.txt","r",stdin);
#endif
int n = read();
scanf("%s",str);
SA::build(str, n, 128);
for(int i=0; i<n; ++i)
pri[SA::ra[i]] = read();
//以下与字符串无关
for(int i=2; i<=n; ++i)
ids[height[i]].push_back(i);
ans_max[n] = LLONG_MIN;
ufs.init(n, pri);
for(int v=n-1; v>=0; --v)
{
ans_cnt[v] = ans_cnt[v+1];
ans_max[v] = ans_max[v+1];
for(auto id:ids[v])
{
auto p = ufs.join(id-1, id);
ans_cnt[v] += p.first;
ans_max[v] = max(ans_max[v], p.second);
}
}
for(int v=0; v<n; ++v)
{
if(ans_cnt[v]==0) ans_max[v] = 0;
cout << ans_cnt[v] << " " << ans_max[v] << "\n";
}
return 0;
}
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}