[NOIP模拟赛]相似字符串

来源:互联网 发布:金庸对悟空传评价知乎 编辑:程序博客网 时间:2024/05/17 21:44
题目描述

输入n个字符串s[i],现在需要给它们从0到n-1标号,满足m个限制条件。每个条件形如标号为ai的字符串是标号为bi的字符串的前缀。求标号方案数,答案模10^9+7输出。


输入格式
第1行:1个整数n(n≤50),表示字符串的个数
接下来n行,每行1个字符串s[i](length(s[i])≤50)
接下来1行:1个整数m,表示限制条件的个数

接下来2行:第1行有m个数,描述ai,第2行有m个数,描述bi


输出格式

第1行:1个整数,表示答案


输入样例
4
hideo
hideto
hideki
hide
2
0 0

1 2


输入样例

6


样例说明
只需要把hide编为0号字符串,它是另外3个串的前缀。另3个串的编号可任意排列,故方案数为3!=6



题解:
考虑到前缀限制,容易想到先将给定字符串之间的前缀关系处理出来。添加一个空串为根,所有串之间的前缀关系构成了一个树的结构。
令有特殊限制的字符串总个数为tot,确定了这tot个字符串是哪些字符串后,剩下的字符串可以任意地给予编号,只要最后给答案乘上(n − tot)!即可。
注意到题中的m≤8,也就是tot不超过16个,于是可以在树上进行状压dp。用f (x,S)表示在以x为根的子树中选出|S|个字符串,对应到S所代表的标号集合的方案数。
考虑前缀限制,一个分配标号方案是合法的,当且仅当对于每一对限制(ai, bi),ai在前缀树上是bi 的祖先。由于我们的dp是以子树为状态的,考虑进一步转化这个限制——可以转化为,对于任意一棵子树,任意一个限制(ai, bi)中,或者没有一个编号在其中,或者只有bi在其中,或者ai, bi 均在其中。

根据这个,可以判断一个状态S是否合法。接着,只要在dp过程中,只考虑S合法的f (x,S),就能得到满足前缀限制的标号方案数了。


由于状态的合法性可以预处理出来,dp时可以只枚举合法的状态。

可以证明合法的状态不超过3 ^m个。这是因为,对于一个限制(ai, bi),它在状态S中只有三种合法的存在情况。这样,总的状态数就从n*(2^tot) 降到了n*(3^m)
接下来,只要预处理出所有合法状态S的合法子集A,满足A与S − A均为合法状态,就可以用
O(n *(5^m) )的时间完成dp。
预处理时只要3^tot直接枚举子集判断即可,接下来证明这样做了之后,合法子集的总个数是5^m级别的。
由于A, S-A, S均合法,对于每一对限制(ai, bi),它们在A与S-A中的合法存在情况只有以下5种:
a,b 均在A中;
a ,b 均在S-A中;
a,b 均不在S中;
仅有b在A中;
仅有b在S-A中;

所以这样的组合(S, A)的总个数只有5^m级别。dp时,直接枚举这样的合法组合进行转移。总的复杂度就是O(3^tot+n*(5^m) )了。


#include<cstring>   #include<cstdio>   #include<algorithm>   #include<vector>   using namespace std;   const int Mod=1e9+7; const int N=55; const int M=(1<<16)+5;   void Getin( int &shu ) {       char c; int f=1; shu=0;       for( c=getchar(); c<'0'||c>'9'; c=getchar() ) if( c=='-' ) f=-1;       for( ; c>='0'&&c<='9'; c=getchar() ) shu=shu*10+c-'0';       shu*=f;   }    struct node{ char s[N]; }ch[N]; bool Cmp( node a, node b ) { return strcmp( a.s, b.s )<0; } bool Prefix( node a, node b ) {       int len=strlen(a.s);      for( int i=0; i<len; i++ ) if( a.s[i]!=b.s[i] ) return 0;     return 1; }   int n, rot; vector<int> son[N]; void Build() {     Getin(n); rot=n+1;//增加一个虚拟根     for( int i=1; i<=n; i++ ) scanf( "%s", ch[i].s );     sort( ch+1, ch+n+1, Cmp );       for( int i=1, j=i-1; i<=n; i++, j=i-1 ) {         for( ; j && !Prefix( ch[j], ch[i] ); j-- );//找ch[i]的前缀         if( !j ) j=rot;//若无前缀, 则连到虚拟根         son[j].push_back(i);     } }   int m, a[10], b[10], p[20], pcnt; void Disc() {     Getin(m);       for( int i=1; i<=m; i++ ) Getin( a[i] ), p[++pcnt]=a[i];      for( int i=1; i<=m; i++ ) Getin( b[i] ), p[++pcnt]=b[i];     sort( p+1, p+pcnt+1 );     pcnt=unique( p+1, p+pcnt+1 )-p-1;     for( int i=1; i<=m; i++ ) {//离散化编号, 使其在0~15的范围内         a[i]=lower_bound( p+1, p+pcnt+1, a[i] )-p-1;          b[i]=lower_bound( p+1, p+pcnt+1, b[i] )-p-1;      } }   int ed; vector<int> statu; bool ok[M]; void Judge() {     ed=1<<pcnt;     for( int i=0; i<ed; i++ ) {//状态压缩, 用i的二进制数表示状态         ok[i]=1;        for( int j=1; j<=m; j++ )            if( ( i>>a[j]&1 ) && !( i>>b[j]&1 ) ) {//只有a没有b, i不合法                ok[i]=0; break;            }        if( ok[i] ) statu.push_back(i);    }}  int siz; vector<int> nxt[M]; void Prep() {//预处理出所有合法状态S的合法子集A Judge();    siz=statu.size();     for( int i=0; i<siz; i++ )         for( int t=statu[i]; ; t=(t-1)&statu[i] ) {             if( ok[t] && ok[ statu[i]-t ] )                 nxt[ statu[i] ].push_back(t);             if( !t ) break;         } }   int dp[N][M], g[M]; void Dp( int r ) { Prep();    dp[r][0]=1;     int siz_son=son[r].size();     for( int i=0; i<siz_son; i++ ) {         Dp( son[r][i] );         for( int j=0; j<ed; j++ ) g[j]=dp[r][j], dp[r][j]=0;         for( int j=0; j<siz; j++ ) {             int now=statu[j];             int siz_nxt=nxt[now].size();             for( int k=0; k<siz_nxt; k++ )                 ( dp[r][now]+=1ll*g[ nxt[now][k] ]*dp[son[r][i]][ now-nxt[now][k] ]%Mod )%=Mod;         }     }     if( r==rot ) return;     for( int i=0; i<ed; i++ ) g[i]=dp[r][i], dp[r][i]=0;     for( int i=0; i<siz; i++ ) {         dp[r][ statu[i] ]=g[ statu[i] ];         for( int j=statu[i]; j; j-=(j&-j) )             ( dp[r][ statu[i] ]+=g[ statu[i]-(j&-j)] )%=Mod;     } } void Print() {     int sum=dp[rot][ ed-1 ];     pcnt=n-pcnt;     for( int i=2; i<=pcnt; i++ ) sum=1ll*sum*i%Mod;     printf( "%d\n", sum ); }   int main() {    Build();Disc();    Dp( rot );     Print();     return 0;  }


原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 苹果6s听筒掉漆怎么办 苹果6splus跳屏怎么办 蘑菇街发货好慢怎么办 买手机不给开票怎么办 泰迪狗皮肤病怎么办 狗狗皮肤病严重怎么办? 钢梁高厚比超限怎么办 手机壳按键难按怎么办 层间位移角超限怎么办 淘宝店排名靠后怎么办 如果地震了你会怎么办 地震来了怎么办60字 如果迷路了你会怎么办 吃多了抽烟想吐怎么办 晚上牙疼得要命怎么办 楼梯被火封锁后怎么办 牙齿疼怎么办能快速不疼 我被短信轰炸了怎么办 火警响了在家该怎么办 痔疮肉球变大了怎么办 痔疮长了好几个怎么办 苹果7手机丢了怎么办 如果油锅着火了怎么办 家里电气著火了怎么办 你家油锅起火了怎么办 交通事故后对方不肯去处理怎么办 租的车出了事故怎么办 借的车出了事故怎么办 台风来了怎么办小知识 台中班台风来了怎么办 地震时在五楼怎么办 小事故对方不来怎么办 发生交通事故对方不处理怎么办 当地震来了该怎么办 地震来了该怎么办教案 地震来了怎么办的问题 住30楼的地震了怎么办 在家里地震来了怎么办? 乐高地震来了怎么办? 地震来了该怎么办300字 地震来了该怎么办200