[HDU2243]考研路茫茫——单词情结

题目

传送门 to HDU

传送门 to VJ

思路

考虑一位一位的加入字符,我们只在乎 当前字符串末尾的匹配情况

既然如此,我们用 A C AC AC 自动机上的一个节点来表示状态就可以了。

现在是一个 d p dp dp 转移。似乎要存储是否曾经出现过词根?

其实不用,只需要用所有情况减去一次都没有出现过的情况即可。

向量中加入一个值,用来求累和——就不需要考虑不转移的情况了。

并且是常系数。所以使用矩阵加速。复杂度 O ( n 6 log ⁡ L ) \mathcal O(n^6\log L) O(n6logL)

代码

重大坑点(我因此 W A WA WA 了很多次):一个节点是否为“没有出现过词根”,要检查其 f a i l fail fail 指针,而不是单纯地考虑当前这个点是否为单词末尾。详见代码 64 64 64 行。

#include <cstdio>
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
inline int readint(){
	int a = 0, f = 1; char c = getchar();
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(long long x){
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) writeint(x/10);
	putchar((x%10)^48);
}

# define MB template < typename T >
MB void getMax(T &a,const T &b){ if(a < b) a = b; }
MB void getMin(T &a,const T &b){ if(b < a) a = b; }

const int MaxN = 27;

namespace ACmachine{ // 自动AC机 [\doge]
	struct Node{
		int son[26], fail, val;
	} node[MaxN];
	int cntNode;
	int newNode(){
		int &id = ++ cntNode;
		memset(node[id].son,0,26<<2);
		node[id].fail = node[id].val = 0;
		return id;
	}
	void clear(){ cntNode = -1; newNode(); }

	void insert(char s[],int val){
		int o = 0;
		for(int i=0; s[i]!='\0'; ++i){
			int &d = node[o].son[s[i]-'a'];
			if(d == 0) d = newNode(); o = d;
		}
		node[o].val = val;
	}

	int q[MaxN];
	void build(){
		int *head = q, *tail = q;
		for(int i=0; i<26; ++i)
			if(node[0].son[i] != 0)
				*(tail ++) = node[0].son[i];
		while(head != tail){
			int o = *(head ++);
			for(int i=0; i<26; ++i){
				int d = node[node[o].fail].son[i];
				if(node[o].son[i] != 0){
					node[node[o].son[i]].fail = d;
					*(tail ++) = node[o].son[i];
				} else node[o].son[i] = d;
			}
	// 重大坑点!这个点即使不是直接的末尾,实际上也匹配完成了!
			getMax(node[o].val,node[node[o].fail].val);
	// 重大坑点!这个点即使不是直接的末尾,实际上也匹配完成了!
		}
	}
} using namespace ACmachine;

typedef unsigned long long int_;
struct Matrix{
	int_ a[MaxN][MaxN];
	void clear(){ memset(a,0,MaxN*MaxN<<3); }
	Matrix operator * (const Matrix &b) const {
		Matrix c; c.clear();
		for(int i=0; i<MaxN; ++i)
			for(int k=0; k<MaxN; ++k)
				for(int j=0; j<MaxN; ++j)
					c.a[i][j] += a[i][k]*b.a[k][j];
		return c;
	}
	Matrix &operator *= (const Matrix &b){
		return *this = (*this)*b;
	}
};
Matrix qkpow(Matrix base,int_ q){
	Matrix ans = base; -- q;
	for(; q; q>>=1,base*=base)
		if(q&1) ans *= base;
	return ans;
}

int_ dengbi(int_ base,int q){
	int_ ans = 0, mi = 1;
	for(int i=30; ~i; --i){
		ans *= mi+1, mi *= mi;
		if(q>>i&1)
			ans += (mi *= base);
	}
	return ans;
}

char s[10]; int n, L;
Matrix movement, origin;

int main(){
	while(cin >> n >> L){
		clear(), movement.clear();
		for(int i=1; i<=n; ++i)
			cin >> s, insert(s,i);
		build(), origin.clear();
		for(int i=0; i<=cntNode; ++i){
			if(node[i].val > 0) continue;
			for(int j=0; j<26; ++j)
				movement.a[i][node[i].son[j]] ++;
		}
		for(int i=0; i<=cntNode; ++i)
			if(node[i].val == 0)
				movement.a[i][MaxN-1] = -1; // 统计答案
		movement.a[MaxN-1][MaxN-1] = 1; // 求累加
		origin.a[0][0] = 1; // 从 null 出发
		origin.a[0][MaxN-1] = dengbi(26,L)+1;
		origin *= qkpow(movement,int_(L)+1ull);
		cout << origin.a[0][MaxN-1] << endl;
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值