BZOJ2553: [BeiJing2011]禁忌

2553: [BeiJing2011]禁忌

Time Limit: 20 Sec  Memory Limit: 128 MBSec  Special Judge
Submit: 203  Solved: 75
[Submit][Status]

Description

       Magic Land上的人们总是提起那个传说:他们的祖先John在那个东方岛屿帮助Koishi与其姐姐Satori最终战平。而后,Koishi恢复了读心的能力……

      

如今,在John已经成为传说的时代,再次造访那座岛屿的人们却发现Koishi遇到了新麻烦。

       这次她遇到了Flandre Scarlet——她拥有可以使用禁忌魔法而不会受到伤害的能力。

       为了说明什么是禁忌魔法及其伤害,引入以下概念:

1.字母集A上的每个非空字符串对应了一个魔法。

其中A是包含了前alphabet个小写字母的集合。

2.有一个集合T,包含了N个字母集A上的字符串

T中的每一串称为一个禁忌串(Taboo string

3.一个魔法,或等价地,其对应的串s因为包含禁忌而对使用者造成的伤害按以下方式确定:

           s分割成若干段,考虑其中是禁忌串的段的数目,不同的分割可能会有不同的数目,其最大值就是这个伤害。

      

由于拥有了读心的能力,Koishi总是随机地使用Flandre Scarlet的魔法,可以确定的是,她的魔法正好对应字母集A上所有长度为len的串

但是,Flandre Scarlet所使用的一些魔法是带有禁忌的,由于其自身特性,她可以使用禁忌魔法而不受到伤害,而Koishi就不同了。可怜的Koishi每一次使用对方的魔法都面临着受到禁忌伤害的威胁。

 

       你现在需要计算的是如果Koishi使用对方的每一个魔法的概率是均等的,那么每一次随机使用魔法所受到的禁忌伤害的期望值是多少。

 

Input

第一行包含三个正整数Nlenalphabet

接下来N行,每行包含一个串Ti,表示禁忌串。

 

Output

一个非负实数,表示所受到禁忌伤害的期望值。

 

 

Sample Input

2 4 2

aa

abb

Sample Output

0.75

【样例1解释】
一共有2^4 = 16种不同的魔法。

需要注意的是“aabb”的禁忌伤害是1而不是2。

HINT

100%的数据中N ≤ 5len ≤1091 ≤ alphabet ≤ 26

 

在所有数据中,有不少于40%的数据中:N = 1

 

数据保证每个串Ti的长度不超过15,并且不是空串。

 

数据保证每个Ti均仅含有前alphabet个小写字母。

 

数据保证集合T中没有相同的元素,即对任意不同的ij,有TiTj

 

【评分方法】

 

对于每一组数据,如果没有得到正确的输出(TLEMLERTE、输出格式错误等)得0分。

 

否则:设你的输出是YourAns,标准输出是StdAns

 

MaxEPS = max(1.0 , StdAns)×10-6

 

如果|YourAns – StdAns| ≤ MaxEPS则得10分,否则得0分。


即:你的答案需要保证相对误差或绝对误差不超过10-6

题解:

出题人果然丧心病狂卡精度。。。

这题写题解的人好像不多,也许是我太傻叉了,时光机的代码画面太美我都不敢看了。。。

 首先我们先搞清一个问题,如果给定了一个字符串,那么它的伤害指数是多少。

转化一下就变成 在一个数轴上给定若干条线段,请选出最多的线段并且使得这些线段两两交集为空。

然后这就是一个贪心水题,见http://www.cnblogs.com/zyfzyf/p/4006703.html

我们只要按右端点排序,能取就取。

那假如我们已经构建了一个AC自动机,我们只要走到一个危险节点,就ans++,并且退回到根节点重新开始走。

可以看出,这样正好相当于在模拟上面的贪心过程。

然后我们看看len增加1,我们能干什么

建图如下:

void build()
{
    vis[1]=1;
    q.push(1);long double tmp=1.0/k;
    while(!q.empty())
    {
        int x=q.front();q.pop();
        for0(i,k-1)
        {
            if(!vis[t[x][i]])vis[t[x][i]]=1,q.push(t[x][i]);
            if(v[t[x][i]])
            {
                a.d[x][n]+=tmp;
                a.d[x][1]+=tmp;
            }else a.d[x][t[x][i]]+=tmp;
        }
    }
}

意思就是我们把所有这样的关系找出来,如果len结束时,我们在x。

那如果t[x][i]是根节点,那我们就有1.0/alp的期望使ans+1,并返回根节点,否则我们有1.0/alp的期望走到t[x][i]。

最后我们求长度为给定是从1走到ans(设置为节点n)的期望次数即可。

然后这个矩阵构建出来我们发现 i到j的期望就等于sigma(i到k的期望*k到j的期望)。

这正好是矩阵乘法!

然后我们就可用快速幂加速了。

注意设置a[n][n]=1

代码:

  1 #include<cstdio>
  2 
  3 #include<cstdlib>
  4 
  5 #include<cmath>
  6 
  7 #include<cstring>
  8 
  9 #include<algorithm>
 10 
 11 #include<iostream>
 12 
 13 #include<vector>
 14 
 15 #include<map>
 16 
 17 #include<set>
 18 
 19 #include<queue>
 20 
 21 #include<string>
 22 
 23 #define inf 1000000000
 24 
 25 #define maxn 200+5
 26 
 27 #define eps 1e-10
 28 
 29 #define ll long long
 30 
 31 #define pa pair<int,int>
 32 
 33 #define for0(i,n) for(int i=0;i<=(n);i++)
 34 
 35 #define for1(i,n) for(int i=1;i<=(n);i++)
 36 
 37 #define for2(i,x,y) for(int i=(x);i<=(y);i++)
 38 
 39 #define for3(i,x,y) for(int i=(x);i>=(y);i--)
 40 
 41 #define mod 1000000007
 42 
 43 using namespace std;
 44 
 45 inline int read()
 46 
 47 {
 48 
 49     int x=0,f=1;char ch=getchar();
 50 
 51     while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
 52 
 53     while(ch>='0'&&ch<='9'){x=10*x+ch-'0';ch=getchar();}
 54 
 55     return x*f;
 56 
 57 }
 58 int n,m,k,cnt,t[maxn][26],go[maxn];
 59 bool v[maxn],vis[maxn];
 60 queue<int>q;
 61 char s[maxn];
 62 struct matrix
 63 {
 64     long double d[maxn][maxn];
 65     matrix(){memset(d,0,sizeof(d));}
 66 }a,b,c;
 67 inline void add()
 68 {
 69     scanf("%s",s+1);int len=strlen(s+1),now=1;
 70     for1(i,len)
 71     {
 72         int x=s[i]-'a';
 73         if(!t[now][x])t[now][x]=++cnt;
 74         now=t[now][x];
 75     }
 76     v[now]=1;
 77 }
 78 void bfs()
 79 {
 80     q.push(1);
 81     while(!q.empty())
 82     {
 83         int x=q.front(),y,j;q.pop();
 84         v[x]|=v[go[x]];
 85         for0(i,k-1)
 86         {
 87             j=go[x];
 88             while(j&&!t[j][i])j=go[j];
 89             if(t[x][i])
 90             {
 91                 go[y=t[x][i]]=j?t[j][i]:1;
 92                 q.push(y);
 93             }else t[x][i]=j?t[j][i]:1;
 94         }
 95     }
 96 }    
 97 void build()
 98 {
 99     vis[1]=1;
100     q.push(1);long double tmp=1.0/k;
101     while(!q.empty())
102     {
103         int x=q.front();q.pop();
104         for0(i,k-1)
105         {
106             if(!vis[t[x][i]])vis[t[x][i]]=1,q.push(t[x][i]);
107             if(v[t[x][i]])
108             {
109                 a.d[x][n]+=tmp;
110                 a.d[x][1]+=tmp;
111             }else a.d[x][t[x][i]]+=tmp;
112         }
113     }
114 }
115 inline matrix operator *(matrix &x,matrix &y)
116 {
117     matrix z;
118     for1(i,n)
119      for1(j,n)
120       for1(l,n)
121        z.d[i][j]+=x.d[i][l]*y.d[l][j];
122     return z;
123 }
124 void ksm(int cs)
125 {
126     for(;cs;cs>>=1,a=a*a)
127         if(cs&1)b=b*a;
128 }
129 void printb()
130 {
131     for1(i,n)for1(j,n)cout<<i<<' '<<j<<' '<<b.d[i][j]<<endl;
132 }
133 void printa()
134 {
135     for1(i,n)for1(j,n)cout<<i<<' '<<j<<' '<<a.d[i][j]<<endl;
136 }
137 
138 int main()
139 
140 {
141 
142     freopen("input.txt","r",stdin);
143 
144     freopen("output.txt","w",stdout);
145 
146     n=read();m=read();k=read();cnt=1;
147     for1(i,n)add();
148     bfs();
149     n=cnt+1;
150     build();
151     for1(i,n)b.d[i][i]=1;
152     a.d[n][n]=1;
153     ksm(m);
154     printf("%.7f\n",(double)b.d[1][n]);
155 
156     return 0;
157 
158 }  
View Code

 真是一道综合性的难题+好题!

UPD:以上纯属口胡。。。

一个边权为1的邻接矩阵自乘n次,则a[s][t]代表从s恰好经过n条边到t的路径条数。

根据这一点我们可以推广

令a[i][j]表示一步从i到j的期望,那么a自乘n次就a[s][t]就代表从s恰好经过n条边到达t的期望。

然后我们新建了一给点n=cnt+1

然后我们要求sigma(从1到n恰好经过j条边的期望)j<=len

所以我们要设值a[n][n]=1,因为下一次计算的时候b[1][n]=。。。+b[1][n]*a[n][n]+。。。就可以把上一次的答案累计。

就算是我懂了QAQ

 

posted @ 2014-12-09 18:07  ZYF-ZYF  Views(1058)  Comments(1Edit  收藏  举报