JZOJ 5167 【NOIP2017模拟6.26】下蛋爷

题目大意:

有n个单词,有一个长度为m的文章,现在你先需要知道每个单词在文章内出现的次数。
现在一共进行k轮,每一轮有p的概率保留出现次数最小的那些单词(不保留就是把那些单词删掉)。
求n轮以后,每个单词保留下来的概率.

1<=n<=200,1<=单词长度<=20,1<=m<=10^6

题解:

分两个部分。
第一部分就直接上ac自动机,可以打up来优化。
ac自动机up优化:
up[x]表示x和x的fail链上的第一个是单词结尾的点。
这样查询的时候就会很快(贴瓷砖也有用的上)。
注意单词可能会有重复,单词结尾需要用前向星维护。

第二部分就是概率dp.
设f[i][j]表示第i轮,出现次数第j大的单词们活下来的概率(出现次数相同的算同一个)
f[i][j] = f[i - 1][j - 1] +f[i - 1][j] * p

Code:

#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#define fo(i, x, y) for(int i = x; i <= y; i ++)
using namespace std;

const int Maxn = 205, Maxm = 1000005;

double p;
char a[Maxn][25], s[Maxm];
int n, ls, cc, ans[205], b[205], d[10000], next[10000][27], fail[10000], up[10000], tt;
int final[10000], tot;
struct edge {
    int to, next;
}e[205];
double f[1005][205], sum[1005];

void link(int x, int y) {
    e[++ tot].next = final[x], e[tot].to = y, final[x] = tot;   
}

void insert(int p, char *a) {
    int len = strlen(a + 1), k = 1;
    fo(i, 1, len) {
        if(!next[k][a[i] - 'a']) next[k][a[i] - 'a'] = ++ tt;
        k = next[k][a[i] - 'a'];
    }
    link(k, p);
}

void make_ac() {
    int l = 0, r = 0; d[++ r] = 1;
    while(l < r) {
        l ++; int x = d[l];
        fo(i, 0, 25) {
            int y = next[x][i];
            if(!y) continue;
            int j = fail[x];
            while(j && !next[j][i]) j = fail[j];
            if(next[j][i]) j = next[j][i];
            fail[y] = j; if(!fail[y]) fail[y] = 1;
            up[y] = y; if(!final[y] && y != 1) up[y] = up[fail[y]];
            d[++ r] = y;
        }
    }
}

void Init() {
    tt = 1;
    scanf("%d", &n);
    fo(i, 1, n) {
        scanf("%s", a[i] + 1);
        insert(i, a[i]);
    }
    make_ac();
    scanf("%s", s + 1);
    ls = strlen(s + 1);
}

void Work() {
    int j = 1;
    fo(i, 1, ls) {
        while(j != 1 && !next[j][s[i] - 'a']) j = fail[j];
        if(next[j][s[i] - 'a']) j = next[j][s[i] - 'a'];
        int z = up[j];
        while(z) {
            for(int k = final[up[z]]; k; k = e[k].next)
                ans[e[k].to] ++;
            z = up[fail[z]];
        }
    }
}

bool rank_b(int x, int y) {
    return ans[x] < ans[y]; 
}

void End() {
    scanf("%lf", &p); scanf("%d", &cc);
    fo(i, 1, n) b[i] = i;
    sort(b + 1, b + n + 1, rank_b);
    fo(j, 2, n) f[1][j] = 1;
    f[1][1] = p;
    fo(i, 2, cc) {
        int k = 0;
        fo(j, 1, n) {
            if(j == 1 || ans[b[j]] != ans[b[j - 1]]) {
                k ++;
                f[i][k] = f[i - 1][k - 1] + (f[i - 1][k] - f[i - 1][k - 1]) * p;
            }
        }
    }
    int k = 0;
    fo(i, 1, n) {
        if(i != 1 && ans[b[i]] == ans[b[i - 1]])
            sum[b[i]] = sum[b[i - 1]]; else k ++, sum[b[i]] = f[cc][k];
    }
    fo(i, 1, n) printf("%.3lf ", sum[i]);
}

int main() {
    Init();
    Work();
    End();
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值