hdu2296 Ring
题意:给你m个模板串,第i个模板串每出现一次会产生vi的价值,要求构造一个长度小于等于n的串使得价值最大,有多种方案的时候先选长度最小的,再选字典序最小的.
首先看到这种一堆模板串的题我们就可以想到AC自动机(误,其实我是从AC自动机的题表里看到这道题的).那么我们建出AC自动机并给节点标号.
对于每个节点,我们算出这个节点的价值(实际意义是在这个位置结尾的所有模板串的价值之和,等于从这个节点沿着fail指针能走到的所有单词节点的价值之和,实际算的时候沿fail指针走一步,递推过来就可以了)
那么就变成:一个有向图,一开始在0号节点,每个点有一个点权,每条边代表一个字母,允许走最多n条边,要求走过的点权之和最大,且多种方案时先选步数最小的,再选走过的边形成的字符串字典序最小的.
那么定义f[i][j]表示从i号节点出发再走j步能够获得的最大价值(包括一开始在i号节点得到的价值),DP方程显然,枚举26条出边即可.最后我们枚举f[0][0]到f[0][n],可以找到答案的长度,再递归一下输出方案即可.因为我们输出方案的时候是从前向后考虑所以这样直接输出就是正确的字典序。思路本质上和翻转字符串差不多,不过我觉得我这个方法避免了翻转字符串,可以减小思维难度(让我翻转的话,翻着翻着就懵逼了…).这个状态定义其实类似于期望DP里的逆序定义.
#include<cstdio> #include<cstring> #include<queue> using namespace std; struct node{ node* ch[26],*fail;int val,num; node(int x){memset(ch,0,sizeof(ch));fail=0;val=0;num=x;} }*root;int tot=0; queue<node*> mem; node* newnode(int x){ if(mem.empty())return new node(x); else{ node* p=mem.front();mem.pop(); *p=node(x);return p; } } node* pos[1200]; void Add(char *c,int x){ node* p=root; while(*c){ int t=*c-'a'; if(p->ch[t]==NULL){p->ch[t]=newnode(++tot);pos[tot]=p->ch[t];} p=p->ch[t];++c; } p->val=x; } void getfail(){ queue<node*> q;q.push(root); while(!q.empty()){ node* x=q.front();q.pop();mem.push(x); for(int i=0;i<26;++i){ if(x->ch[i]){ if(x==root)x->ch[i]->fail=root; else x->ch[i]->fail=x->fail->ch[i]; q.push(x->ch[i]);x->ch[i]->val+=x->ch[i]->fail->val; }else{ if(x==root)x->ch[i]=root; else x->ch[i]=x->fail->ch[i]; } } } } int f[55][1200]; char str[105][15];int v[105]; void output(int x,int y){ if(x){ int t=0; for(int i=1;i<26;++i){ if(f[x-1][pos[y]->ch[i]->num]>f[x-1][pos[y]->ch[t]->num])t=i; } printf("%c",'a'+t);output(x-1,pos[y]->ch[t]->num); } } int main(){ int tests;scanf("%d",&tests); while(tests--){ int n,m;scanf("%d%d",&n,&m);root=newnode(0);pos[0]=root;tot=0; for(int i=1;i<=m;++i)scanf("%s",str[i]); for(int i=1;i<=m;++i)scanf("%d",v+i); for(int i=1;i<=m;++i)Add(str[i],v[i]); getfail(); for(int i=0;i<=tot;++i)f[0][i]=pos[i]->val; for(int i=1;i<=n;++i){ for(int j=0;j<=tot;++j){ f[i][j]=0; for(int k=0;k<26;++k){ int tmp=pos[j]->val+f[i-1][pos[j]->ch[k]->num]; if(tmp>f[i][j])f[i][j]=tmp; } } } int len=0; for(int i=1;i<=n;++i){ if(f[i][0]>f[len][0])len=i; } output(len,0);printf("\n"); } return 0; }