P3294 [SCOI2016]背单词
贪心+字典树
很显然,填一个单词前肯定要优先把所有是它后缀的单词填掉
考虑怎么判后缀
可以把单词倒过来,加入字典树
那么填一个单词前要先填掉它所有祖先的单词
即要从深度小的填到深度大的
可以按搜索顺序填
考虑要怎样确定搜索顺序
随便画一颗树
发现深度优先比广度优先更优
在深度优先时 单词少的子树优先更优
所以可以把每个节点所有的儿子按子树大小排一遍序
然后深搜,更新答案
估了一波复杂度好像可以接受
WA了6个点...
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> using namespace std; const int N=510007; int n,tot,len; long long ans; char s[N]; inline void read_s() { len=0; char c=getchar(); while(c<'a'||c>'z') c=getchar(); while(c>='a'&&c<='z') { s[len++]=c; c=getchar(); } } struct node { int sz,id; }nex[N][27]; inline bool cmp(const node &a,const node &b){ return a.sz<b.sz; } int ch[N][27],cnt,sz[N];//sz是每个节点的子树大小 bool pd[N];//结束标记 int dfs1(int x) //第一遍深搜处理搜索顺序 { for(int i=1;i<=26;i++) { if(!ch[x][i]) continue; nex[x][i].sz=dfs1(ch[x][i]);//遍历儿子 nex[x][i].id=i; sz[x]+=nex[x][i].sz;//更新sz } sort(nex[x]+1,nex[x]+27,cmp);//对儿子按sz排序 return sz[x]; } void dfs2(int x,int las) //las是最近的有结束标记的祖先 { if(pd[x])//如果有结束标记 { tot++; ans+=(tot-las);//更新答案 las=tot; } for(int i=1;i<=26;i++) { int v=nex[x][i].id; if(!ch[x][v]) continue; dfs2(ch[x][v],las);//搜下去 } } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { read_s(); int u=0; for(int j=len-1;j>=0;j--) { int v=s[j]-'a'+1; if(!ch[u][v]) ch[u][v]=++cnt; u=ch[u][v]; if(!j) pd[u]=sz[u]=1; }//建立字典树 } dfs1(0); dfs2(0,0); cout<<ans; return 0; }
调试半天发现是自己算法的问题...
如果是这样一颗树:
那么我深搜的顺序就是先向右边两个跑
右边跑完了再往左边跑
但是应该先往最左边的一个跑....
解决方法很简单
把中间没用的白色节点去掉
把根和蓝色节点的关系拿出来重新建一颗树
再按子树大小一个个深搜
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #include<vector> using namespace std; const int N=510007; int n,tot,len; long long ans; char s[N]; inline void read_s() { len=0; char c=getchar(); while(c<'a'||c>'z') c=getchar(); while(c>='a'&&c<='z') { s[len++]=c; c=getchar(); } } int ch[N][27],cnt,sz[N],f[N]; bool pd[N]; inline bool cmp(const int &a,const int &b){ return sz[a]<sz[b]; } vector <int> v[N];//新图用vector存很方便 void rebuild(int x,int fx)//重建 { int t= pd[x] ? x : fx; for(int i=1;i<=26;i++) { int u=ch[x][i]; if(!u) continue; rebuild(u,t); } if(pd[x]) v[fx].push_back(x); } void prework(int x)//对儿子的顺序排序 { int l=v[x].size(); for(int i=0;i<l;i++) { int u=v[x][i]; prework(u); sz[x]+=sz[u]; } sort(v[x].begin(),v[x].end(),cmp);//骚操作 } void dfs(int x,int fa)//深搜更新答案 { if(x) f[x]=++tot; ans+=(f[x]-f[fa]); int l=v[x].size(); for(int i=0;i<l;i++) dfs(v[x][i],x); } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { read_s(); int u=0; for(int j=len-1;j>=0;j--) { int x=s[j]-'a'+1; if(!ch[u][x]) ch[u][x]=++cnt; u=ch[u][x]; if(!j) pd[u]=sz[u]=1; }//建字典树 } rebuild(0,0); prework(0); dfs(0,0); cout<<ans; return 0; }