文章目录
Trie树
P4683 [IOI2008] Type Printer 打印机
题目链接
就是求最小操作数嘛 一开始想太多,其实就是最长的最后打印呗
那就先标记一下最长的那一串 然后dfs先跳过那些最长串经过的节点
最后再枚举最长串的节点就行啦
细节看代码(也没啥细节啦
AC代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 5e6 + 10;
int ch[N][26],cnt,fl[N],mx[N];
string s,a,ss;char t[N];
int n,tot;
template <class T>
inline void read(T& x){
x = 0;int f = 1;
char c = getchar();
while(!isdigit(c)){
if(c == '-') f = -1;
c = getchar();
}
while(isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48),c = getchar();
x *= f;
}
void insert(string s){
int u = 0;
for(int i = 0;s[i];i ++){
int v = s[i] - 'a';
if(!ch[u][v]) ch[u][v] = ++ cnt;
u = ch[u][v];
t[u] = v + 'a';
}
fl[u] = 1;
}
void dfs(string a){
int u = 0;
for(int i = 0;a[i];i ++){
u = ch[u][a[i] - 'a'];
mx[u] = 1;
}
}
void solve(int u){
if(fl[u]){
tot ++;
ss += 'P';
}
if(tot == n){
cout << ss.size() << endl;
for(int i = 0;ss[i];i ++){
cout << ss[i] << endl;
}
return;
}
for(int i = 0;i < 26;i ++){
if(ch[u][i] && !mx[ch[u][i]]){
ss += t[ch[u][i]];
solve(ch[u][i]);
ss += '-';
}
}
for(int i = 0;i < 26;i ++){
if(ch[u][i] && mx[ch[u][i]]){
ss += t[ch[u][i]];
solve(ch[u][i]);
ss += '-';
}
}
}
int main(){
read(n);
int mxl = 0;
for(int i = 0;i < n;i ++){
cin >> s;
insert(s);
if(mxl < s.size()){
mxl = s.size();
a = s;
}
}
dfs(a);
solve(0);
return 0;
}
AC自动机
P2444 [POI2000]病毒
题目链接
fail跳来跳去挺好玩的,不这道题咱不跳fail
我们标记一下病毒串,然后在trie图上判断是否存在一个环,且环上没有病毒标记
dfs判环就行啦
AC代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 3e5 + 10;
int ch[N][2],cnt,fail[N],fl[N];
bool vis[N],in[N],flag;
char s[N];
void insert(char s[]){
int u = 0;
for(int i = 0;s[i];i ++){
bool v = s[i] - '0';
if(!ch[u][v]) ch[u][v] = ++ cnt;
u = ch[u][v];
}
fl[u] = 1;
}
void getfail(){
queue<int> q;
for(int i = 0;i < 2;i ++) if(ch[0][i]) q.push(ch[0][i]);
while(!q.empty()){
int u = q.front();q.pop();
for(int i = 0;i < 2;i ++){
if(ch[u][i]){
fail[ch[u][i]] = ch[fail[u]][i];
fl[ch[u][i]] |= fl[fail[ch[u][i]]];
q.push(ch[u][i]);
}
else ch[u][i] = ch[fail[u]][i];
}
}
}
void dfs(int u){
if(in[u]){
flag = 1;
return;
}
if(vis[u] || fl[u]) return;
in[u] = vis[u] = 1;
dfs(ch[u][0]);
dfs(ch[u][1]);
in[u] = 0;
}
int n;
int main(){
scanf("%d",&n);
for(int i = 0;i < n;i ++){
scanf("%s",s);
insert(s);
}
getfail();
flag = 0;
dfs(0);
if(flag) puts("TAK");
else puts("NIE");
return 0;
}
回文自动机
P5496 【模板】回文自动机(PAM)
题目链接
就是一模板题嘞 封装写起来好舒服
AC代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 2e6 + 10;
struct PAM_t{
int ch[26],fail,len,num;
};
struct PAM{
PAM_t b[N];
char s[N];
int cur,n,cnt;
PAM(){
b[0].len = 0,b[1].len = -1;
b[0].fail = 1,b[1].fail = 0;
cnt = 1;
cur = 0;
}
void read(){
scanf("%s",s);
}
int getfail(int x){
while(s[n - b[x].len - 1] != s[n] || n - b[x].len - 1 < 0) x = b[x].fail;
return x;
}
void insert(){
int pre = getfail(cur);
if(!b[pre].ch[s[n]]){
b[++ cnt].len = b[pre].len + 2;
int tmp = getfail(b[pre].fail);
b[cnt].fail = b[tmp].ch[s[n]];
b[cnt].num = b[b[cnt].fail].num + 1;
b[pre].ch[s[n]] = cnt;
}
cur = b[pre].ch[s[n]];
}
void solve(){
int k = 0;
for(n = 0;s[n];n ++){
s[n] = (s[n] - 97 + k) % 26 + 97;
s[n] -= 'a';
insert();
printf("%d ",b[cur].num);
k = b[cur].num;
}
}
}P;
int main(){
P.read();
P.solve();
return 0;
}
P3649 [APIO2014]回文串
题目链接
也是模板啦 就是要处理一下出现次数而已
和AC自动机的处理方式很像的
记得在外面把短的回文串出现次数加上长串出现次数就行啦(前提是短串是长串的子串,fail指向的不就是最长回文后缀吗,后缀不就是子串了吗)
AC代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int N = 2e6 + 10;
struct PAM_T{
int ch[26],fail,len,num;
};
struct PAM{
PAM_T b[N];
char s[N];
int cnt,n,cur;
PAM(){
b[0].len = 0,b[1].len = -1;
b[0].fail = 1,b[1].fail = 0;
cnt = 1;
cur = 0;
}
void read(){
scanf("%s",s);
}
int getfail(int x){
while(s[n - b[x].len - 1] != s[n]) x = b[x].fail;
return x;
}
void insert(){
int pre = getfail(cur);
if(!b[pre].ch[s[n]]){
b[++ cnt].len = b[pre].len + 2;
int tmp = getfail(b[pre].fail);
b[cnt].fail = b[tmp].ch[s[n]];
b[pre].ch[s[n]] = cnt;
}
cur = b[pre].ch[s[n]];
b[cur].num ++;
}
void solve(){
for(n = 0;s[n];n ++){
s[n] -= 'a';
insert();
}
long long ans = 0;
for(int i = cnt;i;i --){
b[b[i].fail].num += b[i].num;
if(b[i].num * 1ll * b[i].len > ans) ans = b[i].num * 1ll * b[i].len;
}
printf("%lld\n",ans);
}
}P;
int main(){
P.read();
P.solve();
return 0;
}
P4287 [SHOI2011]双倍回文
题目链接
需要多维护一个trans数组,用来表示长度小于或等于当前节点长度一半的最长回文后缀 更新方法和fail数组大同小异
新建一个节点后,如果这个节点的长度<=2,那么这个节点的trans直接指向这个节点的fail(因为fail的长度肯定<=1)
否则,就跳fail,判断是否能够扩展以及长度是否满足(和fail更新差不多)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 5e5 + 10;
int ch[N][26],fail[N],len[N],trans[N];
int n,cnt,ans,cur;
char s[N];
int getfail(int x,int i){
while(s[i - len[x] - 1] != s[i]) x = fail[x];
return x;
}
void build(){
for(int i = 0;s[i];i ++){
int x = s[i] - 'a';
int p = getfail(cur,i);
if(!ch[p][x]){
len[++ cnt] = len[p] + 2;
fail[cnt] = ch[getfail(fail[p],i)][x];
ch[p][x] = cnt;
if(len[cnt] <= 2) trans[cnt] = fail[cnt];
else{
int tmp = trans[p];
while(s[i - 1 - len[tmp]] != s[i] || (len[tmp] + 2) * 2 > len[cnt]) tmp = fail[tmp];
trans[cnt] = ch[tmp][x];
}
}
cur = ch[p][x];
}
}
int main(){
scanf("%d",&n);
scanf("%s",s);
fail[0] = 1,fail[1] = 0;
len[0] = 0,len[1] = -1;
cur = 0,cnt = 1;
build();
for(int i = 2;i <= cnt;i ++){
if((len[trans[i]] * 2) == len[i] && len[i] % 4 == 0)
ans = max(ans,len[i]);
}
printf("%d\n",ans);
return 0;
}
以后有机会会写篇回文自动机的博客的(毕竟它比较难)
主要是后缀数组还没学透啊啊啊啊啊好难