反抗希碧拉系统续(NOIP2014八校联考第3场第1试10.4)

看了题解才会做的题。。感觉有点思想有点妙就来写一发吧。。。

题意简版

  给出一个特殊的正则表达式,有如下递归定义:

  元素:=“[”+字符集+“]”,表示匹配字符集中的任意一个字符。“+”表示字符串的连接。
  表达式:=元素或表达式+表达式或“(”+表达式+“)”+“+”。相连的表达式表示匹配连续的字符。“+”表示前面括号中的内容出现一次或多次。
  例如:表达式[a][b]匹配ab;表达式[ab][c]匹配ac 或bc;表达式([a][b])+([c])+可以匹配“abc”、“ababc”、“ababccc”等串。
  求出该表达式可以构造出多少不同的长为L的字符串。
  元素个数<=5,L<=10^9,字符集由小写字母组成且每个元素内字符不会重复。

题解

  暴力枚举所有可能的串肯定要超的。。。

  首先我们可以设想一下任何一个串产生的方式:

    从左往右进行,遇到一个元素,即随机选取一个字符;

           若是遇到右括号,即可选择回到与它匹配的左括号开始继续进行从左往右的产生过程(当然可以不选择)。

           遇到“+”可以选择无视。

    当你到达了第最后一个元素,你可以选择结束了,也就产生了一个串。

    如果没有长度限制,产生过程可以一直下去。

    这就是暴力枚举的方式。。。

  可以建一张简单的图:

    我们发现在右括号时可以回到左边的某一位置,也可以继续往右匹配,而在其他符号只能往右走。这是不是很像一张有向图?

    怎么构建图上的点与边呢?我们发现只有元素会对字符串产生影响,我们暂且可以把它们当作点,点权即是元素内的任何一个字符。

    我们又可知道一组括号的效果其实等价于左边最靠近当前右括号的元素可以回到与它相匹配的左括号的右边第一个元素。剩下的边则是相邻元素之间的有向边了。

  大概可以想怎么计数了?

     剩下的就是统计所有不同的串了。

    单单考虑所有产生方式不同的串应该很容易(即任何串产生的路径不同,但可能会是同一个串),随手递推一发即可以了:

          f[i][j]表示当前字符串长度为i,停在第j号元素,转移式子应该比较容易。

    但我们目的是求所有不相同的串的数目,而这样会无法避免重复的串。

    我们可以继续从暴力构串入手:

    对于当前长为i的串来说,若是这个串走往不同方向的元素,但是元素里选择的字符是一样的,那么就产生了相同的长为i+1的串。

    我们是否有方法不走可能产生同一个串的路?仅仅是我认为,应该不能。

    我们可以先换个思路:是否可以记录一下当前有哪些长为i的串重复了?----------可以吧,至少题解里是这么做的。。。。。

    对于当前的串,走往若干元素,可能会产生相同的串,为了把这些串在计数时只计一次,感性地说,我们可以始终跟踪这些串。

    也就是说我们把只能走往特定元素才能产生的同一种串都记下来。

    走往某些元素会产生的同一种串?对于当前的这个长为i串来说,即是走向的不同元素内有同一个字符,即会产生相同长i+1的串。

    观察一下数据范围,元素个数不多于5个?怎么做呢?

    前面几段话其实有点暗示了,就是枚举所有可能走向的元素集合(2^5个),把所有只能通过这些元素集合才能产生的长为i+1的相同串算出来。那么对于一种串它只可能被记在一个状态内了。   

    你可能会问?如果同一种串可在不同的元素位置产生,但是它们走的下一步依然会产生同一种串该怎么办?我们可以对集合与集合之间的状态转移。

    直接上转移式吧 g[i][x] 表示只有在这些集合内的元素作为结束位置才能产生的长为i串(x即是一个集合内元素状压值),那么可以继续按照走向不同元素但产生同一个串的方式继续计数。

            a[x][y]表示从x集合到y集合所有产生同一种串的情况(xy都是两个状压集合)

    那么很好推了吧,g[i+1][y]=g[i][x]*a[x][y]。由于串长度右10^9那么长,按照通常方法,对于这个类似矩乘的转移式直接用快速幂优化掉了。

    时间复杂度O(2^(3*m)*logL)     m为元素个数,L为串长度。

代码

 1 #include <iostream>
 2 #include <stack>
 3 #include <cstring>
 4 #include <cstdio>
 5 #define N 70
 6 #define un unsigned
 7 using namespace std;
 8 char str[1000];
 9 un int sum;
10 un int temp[N][N],A[N][N],ans[N][N],B[N][N];
11 int len,tot,T;
12 stack<int> Q;
13 bool edge[6][6],f[6][30];
14 void work(un int C[][N],un int A[][N],un int B[][N])
15 {
16     for (int i=0;i<N;++i)
17         for (int j=0;j<N;++j)
18         {
19             temp[i][j]=0;
20             for (int k=0;k<N;++k)
21                 temp[i][j]+=A[i][k]*B[k][j];
22         }
23     for (int i=0;i<N;++i)
24         for (int j=0;j<N;++j)
25             C[i][j]=temp[i][j];            
26 }
27 void exp(un int B[][N],un int A[][N],int Time)
28 {
29     if (Time==0)
30     {
31         for (int i=0;i<N;++i)
32             for (int j=0;j<N;++j)
33                 if (i==j)    B[i][j]=1;
34                 else B[i][j]=0;
35         return;
36     }
37     exp(B,A,Time>>1);
38     work(B,B,B);
39     if (Time&1)    work(B,B,A);
40 }
41 int main()
42 {
43     scanf("%s",str);
44     len=strlen(str);
45     for (int i=0;i<len;++i)
46         if (str[i]=='[')    ++tot;
47         else    if (str[i]>='a' && str[i]<='z')    f[tot][str[i]-'a']=1;
48         else    if (str[i]=='(')    Q.push(tot+1);
49         else    if (str[i]==')')    edge[tot][Q.top()]=1,Q.pop();
50     for (int i=0;i<tot;++i)
51         edge[i][i+1]=1;
52     for (int i=1;i<(1<<tot);++i)
53         for (int ch=0;ch<26;++ch)
54         {
55             int s=0;
56             for (int j=1;j<=tot;++j)
57                 if (i&1<<j-1)
58                     for (int k=1;k<=tot;++k)
59                         if (edge[j][k] && f[k][ch]) s|=1<<k-1;
60             if (s)    ++A[i][s];
61         }
62     for (int ch=0;ch<26;++ch)    A[0][1]+=f[1][ch];
63     ans[0][0]=1;
64     scanf("%d",&T);
65     exp(B,A,T);
66     work(ans,ans,B);
67     sum=0;
68     for (int i=1<<tot;i;--i)
69         if (i&1<<tot-1)    sum+=ans[0][i];
70     cout<<sum<<endl;
71     return 0;
72 }
View Code

  

原文地址:https://www.cnblogs.com/Bleacher/p/7242874.html