HDU 6405 Make ZYB Happy 后缀自动机 前缀和优化

**

为这场多校的出题人点赞,题目很有趣!!!,质量很高

**

Make ZYB Happy

Time Limit: 2000/2000 MS (Java/Others) Memory Limit: 262144/262144 K (Java/Others)
Total Submission(s): 43 Accepted Submission(s): 22

Problem Description
It’s known to all that ZYB is godlike, so obviously he has a large number of titles, such as jsking, bijingzyb and nbazyb. ZYB likes his titles very much.

Each of ZYB’s titles is a string consisting of lower case letters ‘a’-‘z’ associated with a happiness value hi, which shows how much ZYB likes this title. If you say any substring of some title with happiness value x, he will get x happiness points. Moreover, a string may appear in more than one title. In this case, the happiness points ZYB gets are multiplied. If the string you say is not the substring of any of his titles, he gets no happiness point.

For example, let’s say ZYB has two titles: zybnb (with happiness value 3) and ybyb (with happiness value 5). If you say y, b or yb, ZYB will get 15 happiness points; if you say z, zy or zyb, ZYB will only get 3 happiness points; if you say ybz or ybac he will get 0 happiness points.

One day, you find ZYB pretty sad. As a big fan of ZYB, you want to say a word to ZYB to cheer him up. However, ZYB is really busy, so you can only say no more than m letters. As you haven’t seen ZYB for a long time, you are so excited that you forget what you want to say, so you decide to choose to say a nonempty string no longer than m and only containing ‘a’-‘z’ with equal probability. You want to know the expectations of happiness points you will bring to ZYB for different m.

Input
The first line contains an integer n (1≤n≤104), the number of titles ZYB has.

The i-th of the next n lines contains a nonempty string ti, which only contains lower case letters ‘a’-‘z’, representing the i-th title. The sum of lengths of all titles does not exceed 3×105.

Then follows a line with n integers hi (1≤hi≤106), the happiness value of i-th title.

The next line is a single integer Q (1≤Q≤3×105), the number of queries.

For the next Q lines, each contains a single integer m (1≤m≤106), meaning that you can say no more than m letters to ZYB.

The input data contains only one test case.

Output
For each query, display a single line of integer, representing the answer. It can be proved that the answer can be uniquely written as p/q where p and q are non-negative integers with gcd(p,q)=gcd(q,109+7)=1, and you should display p⋅q−1mod(109+7), where q−1 means the multiplicative inverse of q modulo 109+7.

Sample Input
2
zybnb
ybyb
3 5
4
1
2
3
4

Sample Output
769230776
425925929
891125950
633120399
Hint

For the first query, you can bring him 3 happiness points if you say “z” or “n”, and 15 happiness points if you say “y” or “b”; all other strings of length 1 bring no
happiness point to ZYB. Therefore, the expectation is (2×3+2×15)/26 = 18/13, and the answer is 18×13^(-1) mod (10^9+7) = 769230776.

题意 :
给出N个字符串。 每个字符串有一个权值val[i]
现在可以构造出一种串,其收获为给出的N个字符串中合法串的权值乘积之和。
如果一个串的子串集合包含构造出来的串,则称这个串合法。
现在给出一个数m,要你求出随机构造一个长度小于等于m的串的收获期望。

解题思路:
本质问题就是分别求不同子串个数,不过这个问题给出了N个串,每个串还都有自己的权值。
直接暴力去求那是N^2的复杂度,显然是无法接受的。
考虑如何化简状态。
后缀自动机就是处理这种问题的非常优秀的算法。
没学过后缀自动机的同学请移步
hihocode 业界良心,讲得非常详细。
先考虑N=1 的情况。
用后缀自动机处理出所有状态,然后再沿着路径dfs一遍,求出每个状态对答案的贡献。
不过要注意的是,这里用的是状态转移路径,而不是后缀链接路径。
状态转移路径是一个有向无环图,所以直接dfs暴力求出每个状态对每种长度的贡献复杂度会是O(N^2)级别的(md,一开始没想清楚这里,这里被坑了两个小时)
考虑到后缀自动机的每个状态包含的子串是连续的后缀,所以,可以使用前缀和的思想进行优化。这样,复杂度就降到了O(N) 。已经非常优秀了。

再考虑N>1 的情况。
emmmmm,没什么好考虑的了,跟N=1的做法一模一样,直接在原来的后缀自动机上添加新的串(也就是所谓的广义后缀自动机),
然后一样dfs求一下贡献就好。

然后就可以直接预处理出所有答案,查询的时候直接输出结果即可。

感觉用前缀和优化复杂度的技巧非常巧妙。 学习了。

#include <bits/stdc++.h>
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define x first
#define y second
#define rep(i,a,b) for(int i=a;i<b;++i)
#define per(i,a,b) for(int i=a-1;i>=b;--i)
#define fuck(x) cout<<'['<<#x<<' '<<(x)<<']'
#define FIN freopen("in.txt","r",stdin);
using namespace std;
#define N 1000050
typedef long long LL;
char s[N];
int ch[N][27],len[N],link[N],rd[N],last,tot,rt,n;
int F[N],ans[N];
int cnt[N];
void add(char pos) {
    int x = pos - 'a' + 1, p = last, np = ++tot;
    last = np;
    len[np] = len[p] + 1;
    F[np] = 1LL;
    while (p && !ch[p][x]) ch[p][x] = np, p = link[p];
    if (!p)
        link[np] = rt;
    else {
        int q = ch[p][x];
        if (len[q] == len[p] + 1)
            link[np] = q;
        else {
            int nq = ++tot;
            len[nq] = len[p] + 1;
            memcpy(ch[nq],ch[q],sizeof(ch[q]));
            link[nq] = link[q];
            link[q] = link[np] = nq;
            while (p && ch[p][x] == q) ch[p][x] = nq, p = link[p];
        }
    }
    return ;
}
void init() {
    rt = last = ++tot;
}
//<------------SAM 结束----------------->
const int MOD = 1e9+7;
const int MAX = 1e4+10;
int vis[N];
string  str[N];
long long val[N];
long long sval[N];
long long QANS[N];
long long base[N];
void presolve(string &S,int id,long long V) {
    int len = S.size();
    int now = rt;
    for(int i=0; i<len; i++) {
        char nowc=S[i]-'a'+1;
        now = ch[now][nowc];
        int temp = now;
        while(temp!=1 && vis[temp]!=id) {
            vis[temp] = id;
            val[temp] = val[temp] * V%MOD ;
            val[temp] %= MOD;
            temp = link[temp];
        }
    }
}
bool nodevis[N];
// 利用前缀和思想是O(N) 否则 直接遍历是N^2 的复杂度。
void dfs(int u){
    QANS[len[link[u]]+1] = (QANS[len[link[u]]+1]+val[u])%MOD;
    QANS[len[u]+1] = (QANS[len[u]+1]-val[u]+MOD)%MOD;
    nodevis[u]=1;
    for(int i=1;i<=26;i++){
        if(ch[u][i] && nodevis[ch[u][i]]==0){
            dfs(ch[u][i]);
        }
    }
}
LL quickpow(LL a,LL b){
    LL ans=1;
    while(b){
        if(b&1){
            ans=(ans*a)%MOD;
        }
        a=(a*a)%MOD;
        b>>=1;
    }
    return ans;
}
int main() {
    //cout<<"yes"<<endl;
    base[0]=1;
    for(int i=1;i<=N;i++){
        base[i]=26ll*base[i-1];
        base[i]%=MOD;
    }
    for(int i=2;i<=N;i++){
        base[i]+=base[i-1];
        base[i]%=MOD;
    }
    init();
    ios::sync_with_stdio(false);
    cin>>n;
    for(int i=0; i<n; i++) {
        cin>>str[i];
        int len = str[i].size();
        for(int j= 0; j<len; j++) {
            add(str[i][j]);
        }
        last=rt;
    }
    for(int i=1;i<=tot;i++){
        val[i]=1;
    }
    for(int i=0;i<n;i++){
        cin>>sval[i];
        presolve(str[i],i+1,sval[i]);
    }
    val[0]=val[1]=0;
    dfs(rt);
    //cout<<"yes"<<endl;
    for(int i=1;i<=N-5;i++){
        QANS[i]+=QANS[i-1];
        QANS[i]%=MOD;
    }
    for(int i=1;i<=N-5;i++){
        QANS[i]+=QANS[i-1];
        QANS[i]%=MOD;
    }
    for(int i=1;i<=N/2 ;i++){
        QANS[i]=QANS[i]*quickpow(base[i],MOD-2)%MOD;
    }
    int Q;
    cin>>Q;
    int cnts;
    while(Q--){
        cin>>cnts;
        //cout<<base[cnts]<<endl;
        //cout<<quickpow(base[cnts],MOD-2)<<" "<<QANS[cnts]<<endl;
        cout<<QANS[cnts]<<endl;
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值