Introduction
每次在一开始为空的串$S$的任意位置插入串$p$
给出最终的$S$,求长度最短(相同时字典序最小)的串$p$
Solution:
样例出锅差评啊,让我这种直接看样例选手挂掉50分啊……
所以说不管怎么样都要好好看输入/输出格式,看看有没有多测
非常直观的思路就是$O(len^2)$枚举所有$S$的子串进行判断
暴力的话就是每次选择一个$p$将其删去并继续递归下去
同时对于随机的点可以直接上贪心:每次直接删去第一个$p$
接下来考虑满分算法
首先$O(len^2)$对$p$的枚举无法优化,只能优化判断的过程
发现每个串$p$被分为几段后,中间的每一段都是独立完整的一段
那么一段的合法性取决于其是否能分成几段合法段且剩下的组成当前字符串的前缀
这样将问题拆分后就可以$dp$了,用$dp[l][r]$来表示区间$[l,r]$是否合法,转移如下:
1、$r$属于最外层的串$p$:$dp[l][r]=dp[l][r-1]\&s[r]==sub[(r-l) mod len+1]$
2、$r$在完整的段中:$dp[l][r]=dp[l][r-len*k]\&dp[r-len*k+1][r]$
这样的转移明显不太容易递推,因此用记忆化搜索处理,复杂度上界为$O(len^4)$
不过这其实是一个很松的上界,首先$p$的长度必须为$len$的约数
同时可以对字符集个数判断(虽然我没写),而且由于是记忆化搜索状态数达不到$O(len^2)$
Tips:$string$在未赋值时长度为0,不能访问$string[i]$来赋值,是越界行为,要先$resize()$!
Code:
#include <bits/stdc++.h> using namespace std; #define X first #define Y second typedef long long ll; typedef pair<int,int> P; typedef double db; const int MAXN=205; string s,sub,res; int len,T,dp[MAXN][MAXN],cur; int solve(int l,int r) { if(l>r) return 1; if(~dp[l][r]) return dp[l][r]; for(int mid=r-cur;mid>=l;mid-=cur) if(solve(l,mid)&&solve(mid+1,r)) return dp[l][r]=1; if(s[r-1]==sub[(r-l)%cur]) return dp[l][r]=solve(l,r-1); return dp[l][r]=0; } int main() { cin>>T; while(T--) { bool f=0;cin>>s;len=s.size(); for(cur=1;cur<=len;cur++) if(len%cur==0) { for(int j=0;j<=len-cur;j++) { sub=s.substr(j,cur); memset(dp,-1,sizeof(dp)); if(solve(1,len)) res=f?min(res,sub):sub,f=1; } if(f){cout<<res<<endl;break;} } } return 0; }
Review:
最近看了几篇文章感觉对动规有了更多的理解
动规其实是一种通过状态的定义和转移方程来将问题拆成多个子问题来解决的一种思想
实现方式究竟是递推还是记忆化搜索式的递归其实与其拆分问题的思想无关,用方便的即可
比如思考此题的心路历程:
发现合法字符串的性质,通过定义$dp[l][r]$根据性质将问题拆分写出转移方程
发现转移方程用递归的形式更容易实现,用记忆化搜索解决