您的位置:首页 > 其它

HDU 2243 考研路茫茫——单词情结

2011-07-09 01:13 337 查看
此题其实为综合算法,AC自动机DP+矩阵二分快速求幂。
由于要求的是“至少”包含一个的情况,每次碰到这种词就必然要联想到反面,也就是一个都不包含的情况。
因为是对2的64次方取模,其实直接用unsigned long long类型即可,其结果就是直接对2^64取模,而且为正,输出用%I64u就行了
于是转成了求解长度为1到N的所有可能串减去长度为1到N不含词根的串,其结果就是此题的答案。
首先求解1到N长度的可能串就是26^1+26^2+.........+26^N用矩阵快速求幂和二分求幂和即可。
然后就是关键的不含词根的串怎么解决。

首先用字典树对输入的词根进行建树,没有的儿子标为-1。词根结尾标记为end=1。

然后用AC自动机对所有节点找出其“可行的”状态转移,然后建立虚子节点,虚子节点指向字典树根(空节点,0号节点)。
建立虚子节点的目的是让不存在的节点返回树头重新匹配。
其意义就是:这个节点所代表的字符的下一个字符可以是他的不为结尾(end=1)的子节点所代表的字符。
最重要的是用fail指针找出具有相同前缀的词根,如果具有相同前缀且fail指针指的节点为词根结尾(end=1)那么该节点所在的串必然有相同前缀,所以这个节点的end也为1。
AC自动机部分就完毕了。

然后就是利用AC自动机建立矩阵。
矩阵的坐标是节点的标号,意思就是这个节点能不能转到另一个节点。
所以只要该节点的儿子存在(不等于-1)且该儿子不为结尾(end!=1)就能进行转移(改点值++)。
矩阵建完就是矩阵的二分求幂模板和求和模板了。
附渣代码:
#include <iostream>
#include <queue>
#include <cstdio>
#define MAX 26
#define CH 26
using namespace std;
char s[MAX];
struct Mat{
unsigned long long mat[MAX][MAX];
void e(){
int i,j;
for(i=0;i<MAX;i++)
for(j=0;j<MAX;j++)
mat[i][j]=(i==j);
}
void clr(){
int i,j;
for(i=0;i<MAX;i++)
for(j=0;j<MAX;j++)
mat[i][j]=0;
}
void print(int n,char *s){
int i,j;
for(i=0;i<n;i++){
for(j=0;j<n;j++){
printf("%6I64u ",mat[i][j]);
}
printf("\n");
}
printf("%s\n",s);
}
Mat operator+(Mat b){
Mat c;
c.clr();
int i,j;
for(i=0;i<MAX;i++){
for(j=0;j<MAX;j++){
c.mat[i][j]=mat[i][j]+b.mat[i][j];
}
}
return c;
}
Mat operator*(Mat b){
Mat c;
int i,j,k;
c.clr();
for(i=0;i<MAX;i++){
for(k=0;k<MAX;k++){
if(mat[i][k]==0)continue;
for(j=0;j<MAX;j++){
if(b.mat[k][j]==0)continue;
c.mat[i][j]+=mat[i][k]*b.mat[k][j];
}
}
}
return c;
}
Mat operator^(int p){ //二分求幂
Mat ret,a;
int i,j;
ret.e();
for(i=0;i<MAX;i++)
for(j=0;j<MAX;j++)
a.mat[i][j]=mat[i][j];
for(;p;p>>=1){
if(p&1)ret=ret*a;
a=a*a;
}
return ret;
}
Mat operator-(Mat b){
Mat c;
c.clr();
int i,j;
for(i=0;i<MAX;i++){
for(j=0;j<MAX;j++){
c.mat[i][j]=mat[i][j]-b.mat[i][j];
}
}
return c;
}
}A,B,C,e; //矩阵模板结构体,重载了矩阵运算符。
int siz;
struct _AC{
int chd[CH];
int fail;
bool end;
void init(){
for(int i=0;i<CH;i++)
chd[i]=-1;
end=0;
fail=0;
}
}node[MAX]; //AC自动机/字典树节点
void insert_trie(char *s) //字典树插入函数
{
int i,p;
p=0;
for(i=0;s[i]!='\0';i++){
char c=s[i]-'a';
if(node[p].chd[c]==-1){
node[p].chd[c]=++siz;
node[siz].init();
}
p=node[p].chd[c];
}
node[p].end=1; //结尾标为1.
}
void ac_automaton()
{
queue < int > Q;
int i,father,son;
Q.push(0);
node[0].end=0;
while(!Q.empty()){
father=Q.front();
Q.pop();
node[father].end=node[father].end||node[node[father].fail].end; //该节点的end值等于他的end值或者其fail指针指向的节点的end值。关键点!!!
for(i=0;i<CH;i++){
if(node[father].chd[i]!=-1){
son=node[father].chd[i];
if(father==0)node[father].fail=0;
else {
node[son].fail=node[node[father].fail].chd[i]; //儿子的fail指针指向父亲的fail指针的这个儿子
}
Q.push(son);
}
else{
if(father==0)node[father].chd[i]=0;
else
node[father].chd[i]=node[node[father].fail].chd[i];//建立虚子节点。
}
}
}
}
void matrix()
{
int i,j;
A.clr();
for(i=0;i<=siz;i++){
for(j=0;j<CH;j++){
if(node[i].chd[j]!=-1&&node[node[i].chd[j]].end==0){
A.mat[node[i].chd[j]][i]++; //可以转移就加1代表可以再多出一种转移路径
}
}
}
B.clr();
B.mat[0][0]=1;
}
Mat plmul(Mat c,int k)//二分求幂和。
{
char i,n=0;
bool s[30];
Mat a,b;
while(k>0){
s[n++]=(k&1);
k>>=1;
}
b=a=c;
for(i=n-2;i>=0;i--){
a=a*(b+e);
b=b*b;
if(s[i]){
b=b*c;
a=a+b;
}
}
return a;
}
int main()
{
int i,n,m;
while(scanf("%d%d",&n,&m)!=EOF)
{
A.clr();B.clr();C.clr();e.e();
node[0].init();
siz=0;
for(i=0;i<n;i++){
scanf("%s",s);
insert_trie(s);
}
ac_automaton();
matrix();
A=plmul(A,m);
A=A*B;
B.mat[0][0]=26;B.mat[1][0]=1;
C.mat[0][0]=1;
B=plmul(B,m);
B=B*C;
unsigned long long ans=B.mat[0][0];
for(i=0;i<=siz;i++)
ans-=A.mat[i][0]; //把所有节点的情况全部减掉就是答案了
printf("%I64u\n",ans);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  matrix insert c struct ini 联想