Ural1158 看上去很困难的一道题。
原文地址 http://blog.csdn.net/prolightsfxjh/article/details/54729646
题意:给出n个不同的字符,用这n个字符构成长度为m的字符串,要求每个串的子串都不出现给定的p个串中的任一个,求满足要求的字符串的个数。
AC自动机+dp
因为构成的最终串是由一个字符一个字符添加到字符串尾部构成的,那么如果一个串的后缀如果恰好是某个给定串的前缀时,这个串就可能最终成为非法串。
用k个给定串建立AC自动机,然后从根节点开始递推,
dpij表示递推到第j个字符当前在自动机上的i号节点时的方案数,如果下一个节点是k,且不是危险节点,则把dpij加到dp[k][i+1]里,
跑一遍,然后答案就是所有非危险节点的方案数的和(其实危险节点上都是0)。因为危险节点是给定串的终点或者其后缀节点是危险节点的点,遍历到危险节点的点上的方案必定是包含了给定串的方案,故不能记录这些。
此外这里dpij会很多,故要用高精度整数。笔者自己收藏的大整数类的版,可行长太长了,所以MLE了一发,调小了才过。⊙﹏⊙‖∣
复杂度 O(n^3)
模版串长度很小,所以Trie上节点很少,递推成本也就比较低。
其实这道题就是三个部分, 实现了AC自动机和大整数, 就是一道DP题了,分开来看 理解了KMP算法和Trie树 AC自动机很简单, 大整数朴素的O(n^2)实现也很简单,DP方程也很简单,但是组合起来,就很困难了。
诶 东北欧赛区的题,总是这么~
代码如下 洋洋洒洒12k 当然不是我写的, 不过这份代码非常清晰易懂,反正智障的我看的挺明白。
#include <iostream> #include <cstdio> #include <cstring> #include <queue> #include <map> using namespace std; const int CHAR_SIZE = 51; const int MAX_SIZE = 105; map<char, int> mp; struct AC_Machine{ int ch[MAX_SIZE][CHAR_SIZE], danger[MAX_SIZE], fail[MAX_SIZE]; int sz; void init(){ sz = 1; memset(ch[0], 0, sizeof ch[0]); memset(danger, 0, sizeof danger); } void _insert(char *s){ int n = strlen(s); int u = 0, c; for(int i = 0; i < n; i++){ c = mp[s[i]]; if(!ch[u][c]){ memset(ch[sz], 0, sizeof ch[sz]); danger[sz] = 0; ch[u][c] = sz++; } u = ch[u][c]; } danger[u] = 1; } void _build(){ queue<int> Q; fail[0] = 0; for(int c = 0, u; c < CHAR_SIZE; c++){ u = ch[0][c]; if(u){Q.push(u); fail[u] = 0;} } int r; while(!Q.empty()){ r = Q.front(); Q.pop(); danger[r] |= danger[fail[r]]; for(int c = 0, u; c < CHAR_SIZE; c++){ u = ch[r][c]; if(!u){ch[r][c] = ch[fail[r]][c]; continue; } fail[u] = ch[fail[r]][c]; Q.push(u); } } } }ac; char s[MAX_SIZE]; #include <string> #include <iostream> #include <iosfwd> #include <cmath> #include <cstring> #include <stdlib.h> #include <stdio.h> #include <cstring> #define MAX_L 205 //最大长度,可以修改 using namespace std; class bign { public: int len, s[MAX_L];//数的长度,记录数组 //构造函数 bign(); bign(const char*); bign(int); bool sign;//符号 1正数 0负数 string toStr() const;//转化为字符串,主要是便于输出 friend istream& operator>>(istream &,bign &);//重载输入流 friend ostream& operator<<(ostream &,bign &);//重载输出流 //重载复制 bign operator=(const char*); bign operator=(int); bign operator=(const string); //重载各种比较 bool operator>(const bign &) const; bool operator>=(const bign &) const; bool operator<(const bign &) const; bool operator<=(const bign &) const; bool operator==(const bign &) const; bool operator!=(const bign &) const; //重载四则运算 bign operator+(const bign &) const; bign operator++(); bign operator++(int); bign operator+=(const bign&); bign operator-(const bign &) const; bign operator--(); bign operator--(int); bign operator-=(const bign&); bign operator*(const bign &)const; bign operator*(const int num)const; bign operator*=(const bign&); bign operator/(const bign&)const; bign operator/=(const bign&); //四则运算的衍生运算 bign operator%(const bign&)const;//取模(余数) bign factorial()const;//阶乘 bign Sqrt()const;//整数开根(向下取整) bign pow(const bign&)const;//次方 //一些乱乱的函数 void clean(); ~bign(); }; #define max(a,b) a>b ? a : b #define min(a,b) a<b ? a : b bign::bign() { memset(s, 0, sizeof(s)); len = 1; sign = 1; } bign::bign(const char *num) { *this = num; } bign::bign(int num) { *this = num; } string bign::toStr() const { string res; res = ""; for (int i = 0; i < len; i++) res = (char)(s[i] + '0') + res; if (res == "") res = "0"; if (!sign&&res != "0") res = "-" + res; return res; } istream &operator>>(istream &in, bign &num) { string str; in>>str; num=str; return in; } ostream &operator<<(ostream &out, bign &num) { out<<num.toStr(); return out; } bign bign::operator=(const char *num) { memset(s, 0, sizeof(s)); char a[MAX_L] = ""; if (num[0] != '-') strcpy(a, num); else for (int i = 1; i < strlen(num); i++) a[i - 1] = num[i]; sign = !(num[0] == '-'); len = strlen(a); for (int i = 0; i < strlen(a); i++) s[i] = a[len - i - 1] - 48; return *this; } bign bign::operator=(int num) { char temp[MAX_L]; sprintf(temp, "%d", num); *this = temp; return *this; } bign bign::operator=(const string num) { const char *tmp; tmp = num.c_str(); *this = tmp; return *this; } bool bign::operator<(const bign &num) const { if (sign^num.sign) return num.sign; if (len != num.len) return len < num.len; for (int i = len - 1; i >= 0; i--) if (s[i] != num.s[i]) return sign ? (s[i] < num.s[i]) : (!(s[i] < num.s[i])); return !sign; } bool bign::operator>(const bign&num)const { return num < *this; } bool bign::operator<=(const bign&num)const { return !(*this>num); } bool bign::operator>=(const bign&num)const { return !(*this<num); } bool bign::operator!=(const bign&num)const { return *this > num || *this < num; } bool bign::operator==(const bign&num)const { return !(num != *this); } bign bign::operator+(const bign &num) const { if (sign^num.sign) { bign tmp = sign ? num : *this; tmp.sign = 1; return sign ? *this - tmp : num - tmp; } bign result; result.len = 0; int temp = 0; for (int i = 0; temp || i < (max(len, num.len)); i++) { int t = s[i] + num.s[i] + temp; result.s[result.len++] = t % 10; temp = t / 10; } result.sign = sign; return result; } bign bign::operator++() { *this = *this + 1; return *this; } bign bign::operator++(int) { bign old = *this; ++(*this); return old; } bign bign::operator+=(const bign &num) { *this = *this + num; return *this; } bign bign::operator-(const bign &num) const { bign b=num,a=*this; if (!num.sign && !sign) { b.sign=1; a.sign=1; return b-a; } if (!b.sign) { b.sign=1; return a+b; } if (!a.sign) { a.sign=1; b=bign(0)-(a+b); return b; } if (a<b) { bign c=(b-a); c.sign=false; return c; } bign result; result.len = 0; for (int i = 0, g = 0; i < a.len; i++) { int x = a.s[i] - g; if (i < b.len) x -= b.s[i]; if (x >= 0) g = 0; else { g = 1; x += 10; } result.s[result.len++] = x; } result.clean(); return result; } bign bign::operator * (const bign &num)const { bign result; result.len = len + num.len; for (int i = 0; i < len; i++) for (int j = 0; j < num.len; j++) result.s[i + j] += s[i] * num.s[j]; for (int i = 0; i < result.len; i++) { result.s[i + 1] += result.s[i] / 10; result.s[i] %= 10; } result.clean(); result.sign = !(sign^num.sign); return result; } bign bign::operator*(const int num)const { bign x = num; bign z = *this; return x*z; } bign bign::operator*=(const bign&num) { *this = *this * num; return *this; } bign bign::operator /(const bign&num)const { bign ans; ans.len = len - num.len + 1; if (ans.len < 0) { ans.len = 1; return ans; } bign divisor = *this, divid = num; divisor.sign = divid.sign = 1; int k = ans.len - 1; int j = len - 1; while (k >= 0) { while (divisor.s[j] == 0) j--; if (k > j) k = j; char z[MAX_L]; memset(z, 0, sizeof(z)); for (int i = j; i >= k; i--) z[j - i] = divisor.s[i] + '0'; bign dividend = z; if (dividend < divid) { k--; continue; } int key = 0; while (divid*key <= dividend) key++; key--; ans.s[k] = key; bign temp = divid*key; for (int i = 0; i < k; i++) temp = temp * 10; divisor = divisor - temp; k--; } ans.clean(); ans.sign = !(sign^num.sign); return ans; } bign bign::operator/=(const bign&num) { *this = *this / num; return *this; } bign bign::operator%(const bign& num)const { bign a = *this, b = num; a.sign = b.sign = 1; bign result, temp = a / b*b; result = a - temp; result.sign = sign; return result; } bign bign::pow(const bign& num)const { bign result = 1; for (bign i = 0; i < num; i++) result = result*(*this); return result; } bign bign::factorial()const { bign result = 1; for (bign i = 1; i <= *this; i++) result *= i; return result; } void bign::clean() { if (len == 0) len++; while (len > 1 && s[len - 1] == ' ') len--; } bign bign::Sqrt()const { if(*this<0)return -1; if(*this<=1)return *this; bign l=0,r=*this,mid; while(r-l>1) { mid=(l+r)/2; if(mid*mid>*this) r=mid; else l=mid; } return l; } bign::~bign() { } bign dp[MAX_SIZE][CHAR_MAX]; int main() { freopen("t.txt", "r", stdin); #ifdef LOCAL //freopen("1.out", "w", stdout); int T = 1; while(T--){ #endif // LOCAL //ios::sync_with_stdio(false); cin.tie(0); int n, m, p; scanf("%d%d%d", &n, &m, &p); scanf("%s", s); for(int i = 0; i < n; i++){mp[s[i]] = i;} ac.init(); while(p--){ scanf("%s", s); ac._insert(s); } int i, j, k; for(i = 0; i < ac.sz; i++){ for(j = 0; j <= m; j++){ dp[i][j] = 0; } } ac._build(); dp[0][0] = 1; for(i = 1; i <= m; i++){ for(j = 0; j < ac.sz; j++){ for(k = 0; k < n; k++){ if(!ac.danger[ac.ch[j][k]]){ dp[ac.ch[j][k]][i] += dp[j][i-1]; } } } } bign ans = 0; for(i = 0; i < ac.sz; i++){ans += dp[i][m];} cout << ans << endl; #ifdef LOCAL cout << endl; } #endif // LOCAL return 0; }