Codeforces461E Appleman and a Game 做题心得
2021/6/24 23:28:37
本文主要是介绍Codeforces461E Appleman and a Game 做题心得,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
目录- 思维过程
- 思路总结
- 代码
躺在床上睡不着,想这个题,突然有了一个对的思路,把自己吓醒
思维过程
首先我们要考虑的是,假设 \(s\) 已知,Appleman 的策略是什么
我们发现他的策略非常简单,就是能匹配就继续匹配,直到不能再匹配了,才划分一下
道理也很简单,现在不划进来,没有任何好处:这不会让后面划的更少,甚至可能更多。
现在我们考虑 Toastman,由于我们已经知道了 Appleman 的策略,我么就要根据他的策略,来卡他
考虑一个一个加入字符。每次加入,Appleman都会试着匹配一下,如果还是子串,就纳入原来划的串;如果失配了,就新划一个串出来,把这个字符加入新划的串。
我们要让它划的尽量多,就要尽快失配,也就是尽快的找一个不是 \(t\) 子串的串。什么影响了我们的决策?显然,第一个字符肯定有影响:对于每个字符,要达到失配状态需要的步数是不同的。我们称它为 “入”,就是如何 进入 了当前划的串。
如 t="AAABACADBABBBCBDCACBCCCDDDBDCDD" 的时候,如果是 A 开头,需要 "AAB",三步才能失配;但如果是 D 开头,只用 “DA",两步就能失配。
那我们也不能不管后面,所以我们要知道我们是加了哪个字符时候失配的,对应的称为”出“。
比如刚才 "AAB" 串的 “出” 就是字符 B,原本 AA 还能匹配,加入这个 B 完了就匹配不上,需要新划一个串来
显然,一个串的“出”就是下一个串的“入”,因此我们可以一个接一个的搞,跳着 贪心,来为难这个 Appleman。
如果我们已知了 ”入“ 和 ”出“,接下来策略很明显,只要让中间的串最短就行了。设入为 \(a\),出为 \(b\),记这个最短的长度为 \(f(a,b)\)。形式化的说就是,找到一个串 \(t'\) 使得它开头为 \(a\) 结尾为 \(b\),不是 \(t\) 的子串,且长度尽量短,此时 \(f(a,b)=|t|-1\)。而我们可以,也需要,把这个 \(f\) 预处理出来。
为啥 需要 预处理:有了这个 \(f\),我们就不需要逐个字符的考虑贪心,可以“跳着”贪心,并且对于相同的问题(“入”相同,“出”相同,认为是相同的问题)可以直接用 \(f\) 解决,不再需要每次求了。而且这个 \(f\) 只占用 \(16\) 个空间,能够轻易存下来。
要求解这个 \(f\), 就把 SAM 建出来,枚举 \(a,b\) ,然后找到没有 \(b\) 出边的所有点,处理一下每个点到它们的最短路即可。
优化:由于 \(|s|\le 10^{18}\),而 \(4^{30}>10^{18}\),所以只要长度到 \(30\),就一定能失配。
所以我们并不需要写 SAM,把每个长度 \(\le 30\) 的子串搞到一个 Trie 里就行了
然后我们继续考虑 Toastman 的策略:假设我们预先知道了划分出来的子串要有 \(k\) 个,开头是 \(c_1,c_2...c_k\),那么 \(s\) 的长度至少为 \(f(c_1,c_2)+f(c_2,c_3)...+f(c_{k-1},c_k)+1\) (这里的 \(1\) 是留给最后一个串,它至少要有一个字母,这个字母就是 \(c_k\))。如果这个东西 \(\le n\),那么划出来 \(k\) 个就可行。
我们发现,这个 \(f\) 我们需要“接连的”用里面的东西,于是我们把它看成一张图:\(f(a,b)\) 表示 \(a\) 到 \(b\) 的边权,那么上面这个式子就变成了一个从 \(c_1\) 到 \(c_k\) 的路径,途径 \(k-1\) 条边,可以任意经过点任意次。
现在我们相当于要求一个 \(4\) 个点的图上走 \(k-1\) 条边的权值和的最小值。这个是经典题,把 floyd 的转移看成矩阵乘法,然后把原图的邻接矩阵求出 \(k-1\) 次幂,然后找到最小值就行了。
然后我们的思路就是,二分这个 \(k\) ,然后拿矩阵快速幂判断是否行。
思路总结
感觉这个题的核心在于这个 \(f\)。\(f\) 干了两个事情,
- 找到了问题的本质 (“入”和“出”)
- 把本质相同的问题一块处理 (长度直接就是 \(f(a,b)\),加上就完了,具体是啥看都不看)
而且这个 \(f\) 还有一个很好的性质,它可以持久化发展,能够一个接一个的贪心。并且,有了这个 \(f\) 之后,就是顺理成章的建图→快速幂→二分...这个题便迎刃而解。可以说,把 \(f\) 想出来,这题就没了。
代码
#include <bits/stdc++.h> using namespace std; namespace Flandre_Scarlet { #define N 200005 #define ll long long #define F(i,l,r) for(int i=l;i<=r;++i) #define D(i,r,l) for(int i=r;i>=l;--i) #define Fs(i,l,r,c) for(int i=l;i<=r;c) #define Ds(i,r,l,c) for(int i=r;i>=l;c) #define MEM(x,a) memset(x,a,sizeof(x)) #define FK(x) MEM(x,0) #define Tra(i,u) for(int i=G.st(u),v=G.to(i);~i;i=G.nx(i),v=G.to(i)) #define p_b push_back #define sz(a) ((int)a.size()) #define all(a) a.begin(),a.end() #define iter(a,p) (a.begin()+p) int I() {char c=getchar(); int x=0; int f=1; while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar(); while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return ((f==1)?x:-x);} template <typename T> void Rd(T& arg){arg=I();} template <typename T,typename...Types> void Rd(T& arg,Types&...args){arg=I(); Rd(args...);} void RA(int *p,int n) {F(i,1,n) *p=I(),++p;} ll len; int n; char t[N]; void Input() { scanf("%lld",&len); // 题目里给的n,然而我这里的 n=|t|,就管它叫 len,即 |s| scanf("%s",t+1); n=strlen(t+1); } class Trie { public: #define M 3000006 // 30n int ch[M][4]; int tot; char dep[M]; Trie() {FK(ch); tot=1;} void ins(char *s) { int cur=1; char *p=s; for(int i=1;i<=30 and (*p);++i,++p) // 这里到 30 就行了 { int c=(*p)-'A'; if (!ch[cur][c]) ch[cur][c]=++tot; cur=ch[cur][c]; dep[cur]=i; } } char mark[M]; // 每个节点属于根节点哪个儿子(0~3)的子树 int near[4][4]; void DFS_mark(int u,int f) // f:0~3 { mark[u]=f; F(i,0,3) if (ch[u][i]) DFS_mark(ch[u][i],f); } void sakuya() { F(i,0,3) DFS_mark(ch[1][i],i); MEM(near,0x3f); F(j,0,3) { F(i,1,tot) if (!ch[i][j]) // 这个点没有 j 的转移 { near[mark[i]][j]=min(near[mark[i]][j],(int)dep[i]); // 找到这个点的路径上第一个字符是啥 // 然后这个字符就是 "入", j就是“出” } } } }T; template<const int S> class Matrix { public: ll a[S][S]; Matrix<S>() {MEM(a,0x3f);} ll* operator[](int i) {return a[i];} }; template<const int S> Matrix<S> operator*(Matrix<S> a,Matrix<S> b) { Matrix<S> c; MEM(c.a,0x3f); F(i,0,S-1) F(j,0,S-1) F(k,0,S-1) c[i][k]=min(c[i][k],a[i][j]+b[j][k]); // 类似 floyd 的转移 return c; } template<const int S> Matrix<S> operator^(Matrix<S> a,ll p) { Matrix<S> r; F(i,0,S-1) r[i][i]=0; while(p) { if (p&1ll) r=r*a; a=a*a; p>>=1ll; } return r; } Matrix<4> f; // f[a][b]: 开头为a的串, 要以b跳出自动机, 最短串长 // 形式化的说, 找到一个最短的串s, 使得s第一个为a最后一个为b, s不是t子串, f[a][b]=|s|-1 bool cxk(ll mid) { // f可以看做贪心过程中, 从一个字母转移到另一个字母所需的最小长度 (<=30) // 然后 f^(mid-1) 就是走 mid-1 步 (必定划分成了mid个串) 所需的长度 // 看是否存在长度<=len的方案即可, 若有, 则至少mid个串; 无则<mid个串 Matrix<4> fn=(f^(mid-1)); F(i,0,3) F(j,0,3) if (fn[i][j]<len) return true; // 留下一个位置给最后那个字母 (j) return false; } void Sakuya() { F(i,1,n) T.ins(t+i); T.sakuya(); F(a,0,3) F(b,0,3) { f[a][b]=T.near[a][b]; } ll Low=1,High=len; while(Low<High) { ll mid=(Low+High+1)>>1; if (cxk(mid)) Low=mid; else High=mid-1; } printf("%lld\n",Low); } void IsMyWife() { Input(); Sakuya(); } } #undef int //long long int main() { Flandre_Scarlet::IsMyWife(); getchar(); return 0; }
这篇关于Codeforces461E Appleman and a Game 做题心得的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-01UniApp 中组件的生命周期是多少-icode9专业技术文章分享
- 2024-11-01如何使用Svg Sprite Icon简化网页图标管理
- 2024-10-31Excel数据导出课程:新手从入门到精通的实用教程
- 2024-10-31Excel数据导入课程:新手入门指南
- 2024-10-31RBAC的权限课程:新手入门教程
- 2024-10-31Svg Sprite Icon课程:新手入门必备指南
- 2024-10-31怎么配置 L2TP 允许多用户连接-icode9专业技术文章分享
- 2024-10-31怎么在FreeBSD上 安装 OpenResty-icode9专业技术文章分享
- 2024-10-31运行 modprobe l2tp_ppp 时收到“module not found”消息提醒是什么-icode9专业技术文章分享
- 2024-10-31FreeBSD的下载命令有哪些-icode9专业技术文章分享