【题解】POJ1934 Trip (DP+记录方案)
题意:
刚开始我是这么设状态的(谁叫我DP没学好)
\(dp(i,j)\)表示钦定选择\(i\)和\(j\)的LCS,然而你会发现这样钦定没什么用。
还不如当时初学者的时候的\(dp(i,j)\)表示考虑到\(i\)考虑到\(j\)的LCS...果然经典的是禁得起考验的...
考虑如何记录方案,第一个想法是直接暴力记录从哪转移的,但是这样显然不行。因为有很多重复的元素。
注意到题目保证本质不同的满足答案要求的串的个数是\(O(n)\)级别的,而且字符集也是\(O(n)\)级别的,所以我们考虑一下对于每个串的每一个点的任意一个字符的最近的上一次出现的位置。我们只考虑对于每个字符每个上一次出现的不同位置,就类似于线性筛一样的使得我们对于每个不同的可以记录答案的串只记录了一次。
这样回溯的实质其实就是在从字符串的后端往前端枚举出现哪个字符,由于每次合法的"枚举"一定可以对应上一个不同的可以计入答案的串,所以复杂度就是\(O(n)\)的,而不是\(O(26^{80})\)。
这样的手法应该有更普适的含义,在要输出所有合法方案而且保证所有合法方案的级别时,或许也可以用这个手法来解题。
//@winlere
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
using namespace std; typedef long long ll;
inline int qr(){
register int ret=0,f=0;
register char c=getchar();
while(c<48||c>57)f|=c==45,c=getchar();
while(c>=48&&c<=57) ret=ret*10+c-48,c=getchar();
return f?-ret:ret;
}
const int maxn=81;
int data1[maxn],data2[maxn],sz1,sz2;
int dp[maxn][maxn];
int lastx[maxn][27];
int lasty[maxn][27];
char dfn[maxn];
typedef const int& ct;
set < string > s;
typedef set < string > :: iterator it;
inline void qr(int* p,int& cnt){
cnt=0;
register char c=getchar();
while(c<'a'||c>'z') c=getchar();
while(c>='a'&&c<='z') p[++cnt]=c-'a'+1,c=getchar();
}
void dfs(ct x,ct y,ct len){
if(!len){
s.insert(dfn);
return;
}
if(x<=0||y<=0)return;
for(register int t=1,t1,t2;t<=26;++t){
t1=lastx[x][t];
t2=lasty[y][t];
if(dp[t1][t2]==len){
dfn[len-1]=t+'a'-1;
dfs(t1-1,t2-1,len-1);
}
}
}
int main(){
qr(data1,sz1);
qr(data2,sz2);
for(register int t=1;t<=sz1;++t)
for(register int i=1;i<=sz2;++i){
if(data1[t]==data2[i])
dp[t][i]=dp[t-1][i-1]+1;
if(data1[t]!=data2[i])
dp[t][i]=max(dp[t-1][i],dp[t][i-1]);
}
for(register int t=1;t<=sz1;++t){
for(register int i=1;i<=26;++i)
lastx[t][i]=lastx[t-1][i];
lastx[t][data1[t]]=t;
}
for(register int t=1;t<=sz2;++t){
for(register int i=1;i<=26;++i)
lasty[t][i]=lasty[t-1][i];
lasty[t][data2[t]]=t;
}
dfs(sz1,sz2,dp[sz1][sz2]);
for(it t=s.begin();t!=s.end();++t)
cout<<*t<<'\n';
return 0;
}