训练赛第二场的I题,上完体育课回来就把这题过了,今天训练赛rank1了,还把大大队虐了,而且我还过了这道题 (虽然我也就过了这道题。。。),第一次在比赛中手写AC自动机还带dp的,心情大好。
给一个字符串集合,求包含该集合超过K个字符的,长度为L的字符串的个数。
显然是在AC自动机上跑dp,设dp[u][L][k]表示当前在结点u,还要走L步,当前状态为k的个数。一开始第三维表示的是包含k个字符串,但是题目要求不含重复的,那就只能状压了。转移为dp[u][L][k]+=dp[v][L-1][nk],nk为k经过u之后的状态,即直接沿着失配边往回走,把能配到的都加进来就行了。当cnt(k)>=K是就不用再走了,直接返回26^L。
#include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<queue> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a)) using namespace std; typedef long long ll; const int maxn=252; const int INF=1e9+10; const ll MOD=20090717LL; ll qpow(ll n,ll k,ll p) { ll res=1; while(k){ if(k&1) res=(res*(n%p))%p; n=((n%p)*(n%p))%p; k>>=1; } return res; } ll Cnt(ll k) { ll res=0; for(ll i=0;i<=10;i++){ if(k&(1<<i)) res++; } return res; } int L,M,K; int dp[252][26][(1<<10)+10]; struct Trie { int ch[maxn][26]; int f[maxn]; int last[maxn]; int End[maxn]; int rt,tot; int newnode() { ++tot; memset(ch[tot],-1,sizeof(ch[tot])); End[tot]=0; return tot; } void init() { tot=-1; rt=newnode(); } void insert(char *s,int ID) { int len=strlen(s),u=rt; REP(i,0,len-1){ int c=s[i]-'a'; if(ch[u][c]==-1) ch[u][c]=newnode(); u=ch[u][c]; } End[u]=(1<<ID); } void build() { queue<int> q; f[rt]=rt;last[rt]=rt; REP(c,0,25){ if(~ch[rt][c]) f[ch[rt][c]]=rt,q.push(ch[rt][c]); else ch[rt][c]=rt; } while(!q.empty()){ int u=q.front();q.pop(); REP(c,0,25){ if(~ch[u][c]) f[ch[u][c]]=ch[f[u]][c],q.push(ch[u][c]); else ch[u][c]=ch[f[u]][c]; } if(End[f[u]]) last[u]=f[u]; else last[u]=last[f[u]]; } } int get(int u) { if(u==rt) return 0; return End[u]|get(last[u]); } int dfs(int u,int L,int k) { int &res=dp[u][L][k]; if(~res) return res; if(Cnt(k)>=K) return res=qpow(26,L,MOD); if(L==0) return res=0; res=0; int nk=0; REP(c,0,25){ int v=ch[u][c]; nk=k; if(End[v]) nk|=get(v); else if(last[v]) nk|=get(last[v]); res=(res+dfs(ch[u][c],L-1,nk))%MOD; } return res; } };Trie ac; char s[maxn]; int main() { while(~scanf("%d%d%d",&L,&M,&K)){ if(L==0&&M==0&&K==0) break; //cout<<"L="<<L<<" M="<<M<<" K="<<K<<endl; ac.init(); REP(i,1,M){ scanf("%s",s); if(strlen(s)>L) continue; ac.insert(s,i-1); } ac.build(); memset(dp,-1,sizeof(dp)); printf("%I64d ",ac.dfs(ac.rt,L,0)); } return 0; }
第一次在比赛中过数据结构,心情大好。
但是还是有些要总结的:
首先还是提交MLE之后没有只是稍稍优化而没有优化到不能再优化为止,因此MLE了三次,其实有两次是没必要的,接着没有算cnt的考虑常数,直接循环到20,导致TLE了,其实直接除以2会更好,常数也小。接着是long long和int的选择,一般用longlong的唯一风险就是MLE了,碰到MLE应该马上考虑longlong,或者用之前要先考虑用longlong还是会int。
总之应该提交之前或者写代码之前应该除了考虑多种情况测试边缘数据防止WA之外,还应该考虑时间空间复杂度,防止MLE和TLE,必要的时候还要优化常数。