Living-Dream 系列笔记 第61期
退役选手复活后的第一篇。
https://www.luogu.com.cn/problem/SP4033
其实只要一个 insert.
就是插入时没新建节点 \(\to\) 自己是别人前缀,
插入时途经了别人的结束节点 \(\to\) 别人是自己前缀。
code
#include<bits/stdc++.h> using namespace std; const int N=3e5+5,M=31; int n,cnt,in[M]; int tot,trie[N][M]; vector<int> G[M]; string s[N],ans[N]; bool vis[N]; void insert(string s){ int cur=0; for(int i=0;i<s.size();i++){ if(!trie[cur][s[i]-'a']) trie[cur][s[i]-'a']=++tot; cur=trie[cur][s[i]-'a']; } vis[cur]=1; return; } bool build(string s){ int cur=0; for(int i=0;i<s.size();i++){ if(vis[cur]) return 0; for(int j=0;j<26;j++) if((int)(s[i]-'a')!=j&&trie[cur][j]) G[s[i]-'a'].push_back(j),in[j]++; cur=trie[cur][s[i]-'a']; } return 1; } bool topo(){ queue<int> q; int res=0; for(int i=0;i<26;i++) if(!in[i]) q.push(i); while(!q.empty()){ int cur=q.front(); q.pop(),res++; for(int i:G[cur]){ in[i]--; if(!in[i]) q.push(i); } } return res==26; } int main(){ cin>>n; for(int i=1;i<=n;i++) cin>>s[i],insert(s[i]); for(int i=1;i<=n;i++){ for(int i=0;i<M;i++) G[i].clear(); memset(in,0,sizeof(in)); if(build(s[i])&&topo()) ans[++cnt]=s[i]; } cout<<cnt<<'\n'; for(int i=1;i<=cnt;i++) cout<<ans[i]<<'\n'; return 0; }
https://www.luogu.com.cn/problem/P3065
bf 是全排列字典然后依照他去 sort.
考虑转换问题思考角度,尝试将每个字符串作为第一个。
探索性质:
-
若一个字符串是别人的前缀,则他永远也成不了第一个
-
若一个字符串是第一个,则他向 trie 中与它同层的节点连边,一定不会出现矛盾(即出现环)
第一条很好理解,而第二条实际是说连边后形成的图为 DAG(有拓扑序),这个拓扑序就是满足当前字符串为第一个的字典顺序。
于是我们连边跑 toposort / tarjan 判环即可。
code
#include<bits/stdc++.h> using namespace std; const int N=3e5+5,M=31; int n,cnt,in[M]; int tot,trie[N][M]; vector<int> G[M]; string s[N],ans[N]; bool vis[N]; void insert(string s){ int cur=0; for(int i=0;i<s.size();i++){ if(!trie[cur][s[i]-'a']) trie[cur][s[i]-'a']=++tot; cur=trie[cur][s[i]-'a']; } vis[cur]=1; return; } bool build(string s){ int cur=0; for(int i=0;i<s.size();i++){ if(vis[cur]) return 0; for(int j=0;j<26;j++) if((int)(s[i]-'a')!=j&&trie[cur][j]) G[s[i]-'a'].push_back(j),in[j]++; cur=trie[cur][s[i]-'a']; } return 1; } bool topo(){ queue<int> q; int res=0; for(int i=0;i<26;i++) if(!in[i]) q.push(i); while(!q.empty()){ int cur=q.front(); q.pop(),res++; for(int i:G[cur]){ in[i]--; if(!in[i]) q.push(i); } } return res==26; } int main(){ cin>>n; for(int i=1;i<=n;i++) cin>>s[i],insert(s[i]); for(int i=1;i<=n;i++){ for(int i=0;i<M;i++) G[i].clear(); memset(in,0,sizeof(in)); if(build(s[i])&&topo()) ans[++cnt]=s[i]; } cout<<cnt<<'\n'; for(int i=1;i<=cnt;i++) cout<<ans[i]<<'\n'; return 0; }
https://www.luogu.com.cn/problem/P4407
考虑一种很 bf 的方法:在 trie 上 dfs。但是她能过。
添加 \(\to\) 原串跳一个字符,询问串不动。
删除 \(\to\) 原串不动,询问串跳一个字符。
替换 \(\to\) 原串、询问串各自跳一个字符。
具体细节见代码。
code
#include<bits/stdc++.h> using namespace std; const int N=2e5+5,M=31; int n,m; int tot,trie[N][M]; int tagtot,tagx[N]; bool isword,tag[N],vis[N]; void insert(string s){ int cur=0; for(int i=0;i<s.size();i++){ if(!trie[cur][s[i]-'a']) trie[cur][s[i]-'a']=++tot; cur=trie[cur][s[i]-'a']; } vis[cur]=1; return; } void search(string s,int cur,int len,bool flag){ if(len==s.size()&&vis[cur]&&flag){ isword=1; return; } if(len==s.size()&&vis[cur]&&!flag){ if(!tag[cur]) tag[tagx[++tagtot]=cur]=1; return; } if(flag){ if(len<s.size()) search(s,cur,len+1,!flag); for(int i=0;i<26;i++){ if(trie[cur][i]){ search(s,trie[cur][i],len,!flag); if(i!=(int)(s[len]-'a')) search(s,trie[cur][i],len+1,!flag); } } } if(trie[cur][s[len]-'a']) search(s,trie[cur][s[len]-'a'],len+1,flag); if(len>=s.size()) return; } int main(){ cin>>n>>m; for(int i=1;i<=n;i++){ string w; cin>>w,insert(w); } for(int i=1;i<=m;i++){ string q; cin>>q; for(int j=1;j<=tagtot;j++) tag[tagx[j]]=0,tagx[j]=0; tagtot=0,isword=0; search(q,0,0,1); if(isword) cout<<"-1\n"; else cout<<tagtot<<'\n'; } return 0; }
https://www.luogu.com.cn/problem/P4683
又是上题的套路。
考虑一种贪心:把最长的留在最后打印,这样删除字符数更少。显然(?)这是正确的。
于是我们遇到最长的就在 trie 上标记一遍,dfs 时没标记的先跑即可。
在 trie 上 dfs 就保证了前缀不会重复删去又加上。
以后看到要输出方案都可以考虑 dfs。
code
#include<bits/stdc++.h> using namespace std; const int N=5e5+5,M=26; int m,cnt; string ans,s[25005]; int tot,trie[N][M]; bool vis[N],mk[N]; char ch[N]; void insert(string ss){ int cur=0; for(int i=0;i<ss.size();i++){ if(!trie[cur][ss[i]-'a']) trie[cur][ss[i]-'a']=++tot; cur=trie[cur][ss[i]-'a']; ch[cur]=ss[i]; } vis[cur]=1; } void mark(string ss){ int cur=0; for(int i=0;i<ss.size();i++){ cur=trie[cur][ss[i]-'a']; mk[cur]=1; } } void search(int cur){ if(vis[cur]) cnt++,ans+="P"; if(cnt==m){ cout<<ans.size()<<'\n'; for(int i=0;i<ans.size();i++) cout<<ans[i]<<'\n'; } for(int i=0;i<26;i++){ if(trie[cur][i]&&!mk[trie[cur][i]]){ ans+=ch[trie[cur][i]]; search(trie[cur][i]); ans+="-"; } } for(int i=0;i<26;i++){ if(trie[cur][i]&&mk[trie[cur][i]]){ ans+=ch[trie[cur][i]]; search(trie[cur][i]); ans+="-"; } } } int main(){ cin>>m; int maxz=0,pos=0; for(int i=1;i<=m;i++){ cin>>s[i]; if(s[i].size()>maxz) maxz=s[i].size(),pos=i; } for(int i=1;i<=m;i++){ insert(s[i]); if(pos==i) mark(s[i]); } search(0); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】