【专题】Subsequence
回文子序列
----------------------------
最长回文子序列
题目略
----------------------------
这是回文子序列的基础,直接DP即可。
f[ L ][ R ] 表示区间[ L , R ] 的最长回文子序列长度。
f[ L ][ R ] = 0 ( L > R )
f[ L ][ R ] = 1 ( L = R )
f[ L ][ R ] = f[ L+1 ][ R-1 ] + 2 ( S[ L ]==S[ R ] )
f[ L ][ R ] = max( f[ L+1 ][ R ] , f[ L ][ R-1 ] )
----------------------------
代码略
----------------------------
环形回文子序列
----------------------------
重点是用next() prev() 代替l+1,r-1
在普通的回文子序列dp中 若x>y 则 f[ x ][ y ]没有值
这为环形dp提供了储存空间。
----------------------------
const int maxn=1111; int a[maxn]; int f[maxn][maxn]; int n; int prev(int x){ return (x-1+n)%n; } int next(int x){ return (x+1)%n; } int dp(int l,int r){ if (l==r) return 1; if (next(l)==r) return 1+(a[l]==a[r]); if (f[l][r]!=-1) return f[l][r]; if (a[l]==a[r]) return f[l][r]=dp(next(l),prev(r))+2; return f[l][r]=max(dp(next(l),r),dp(l,prev(r))); } int main() { int ans; while (~scanf("%d",&n)){ if (n==0) break; ans=0; memset(f,-1,sizeof(f)); for (int i=0;i<n;i++){ scanf("%d",&a[i]); } if (n==1) ans=1; else if (n==2) ans=2; else{ for (int i=0;i<n;i++){ ans=max(ans,dp(next(i),prev(i))+1); //ans=max(ans,dp(i,prev(i))); ans=max(ans,dp(next(i),i)); } } printf("%d\n",ans); } return 0; }----------------------------
超长回文子序列
----------------------------
题目给出的n范围较大(5*10^5),要求输出长度为100的回文子序列,若不存在则输出最长的。
注意到隐含条件,序列由小写字母构成,当n>=2600时,必然有某个字母的数量超过100(鸽笼原理),输出100个该字母即可。
注意输出dp方案的思路。
此题也可以用纯dp优化解决。
见题解: http://codeforces.com/blog/entry/8538
代码: http://codeforces.com/contest/335/submission/4221965
----------------------------
const int maxs=55555; const int maxn=3333; char s[maxs]; int n; int cnt[27]; char mc; char ans[maxs]; int t; int f[maxn][maxn]; int dp(int l,int r){ if (l>r) return 0; if (l==r) return 1; //if (l+1==r) return 1+(s[l]==s[r]); if (f[l][r]!=-1) return f[l][r]; if (s[l]==s[r]) return f[l][r]=dp(l+1,r-1)+2; return f[l][r]=max(dp(l,r-1),dp(l+1,r)); } void trace(int l,int r){ if (l>r) return; if (l==r) ans[t++]=s[l]; else if (s[l]==s[r]){ ans[t++]=s[l]; trace(l+1,r-1); ans[t++]=s[r]; } else{ if (f[l+1][r]<f[l][r-1]) trace(l,r-1); else trace(l+1,r); } } int main() { cin>>(s+1); n=strlen(s+1); memset(cnt,0,sizeof(cnt)); if (n>=2600){ for (int i=1;i<=n;i++){ cnt[s[i]-'a']++; } for (int i=0;i<26;i++){ if (cnt[i]>=100){ mc=i+'a'; break; } } for (int i=0;i<100;i++){ ans[i]=mc; } ans[100]=0; cout<<ans<<endl; } else{ memset(f,-1,sizeof(f)); dp(1,n); t=0; trace(1,n); ans[t]=0; //cerr<<"nice"<<endl; if (t<=100) cout<<ans<<endl; else{ //cout<<"boy next door"<<endl; for (int i=0;i<50;i++) cout<<ans[i]; for (int i=t-50;i<t;i++) cout<<ans[i]; cout<<endl; } } return 0; }
----------------------------
回文子序列的数量
HDU 4632 Palindrome subsequence
----------------------------
此题要求输出回文子序列的方案数
当s[i]==s[j]时 区间[i,j]的方案数为回文ij + 区间[i+1,j-1] 的方案数
加上区间[i+1,j] [i,j-1]的方案数,减去区间[i+1,j-1]的方案数(容斥)
----------------------------
const int maxn=1111; int f[maxn][maxn]; char s[maxn]; int main() { int T,n; int cas=0; scanf("%d",&T); while (T--) { scanf("%s",(s+1)); n=strlen(s+1); clr(f,0); for (int i=1;i<=n;i++) { f[i][i]=1; if (s[i]==s[i+1]&&i+1<=n) f[i][i+1]=3; else if (i+1<=n) f[i][i+1]=2; } for (int k=2;k<n;k++) { for (int i=1;i+k<=n;i++) { int j=i+k; if (s[i]==s[j]) f[i][j]=f[i+1][j-1]+1; f[i][j]+=f[i+1][j]+f[i][j-1]-f[i+1][j-1]; f[i][j]=(f[i][j]%MOD+MOD)%MOD; } } printf("Case %d: %d\n",++cas,f[1][n]); } return 0; }----------------------------
----------------------------
公共子序列
----------------------------
最长公共子序列
----------------------------
这应该是最裸的LCS了,直接dp即可
f[ L ][ R ] 表示第一个串的前L个数与第二个穿的前R个数的最长公共子序列的长度。
f[ L ][ R ] = f[ L-1 ][ R-1 ] +1 ( S[ L ]==S[ R ] )
f[ L ][ R ] = max( f[ L-1 ][ R ] , f[ L ][ R-1 ] )
----------------------------
代码略
----------------------------
三个串的最长公共子序列
Topcoder SRM 298 DIV 1 1000. CountingCommonSubsequences
----------------------------
https://apps.topcoder.com/forums/?module=Thread&threadID=510443&start=0&mc=5#537441
听说这是很sb的写法。
以下是岛娘的写法。(完全看不懂 待整理)
----------------------------
#define p1 p1[c] #define p2 p2[c] #define p3 p3[c] #define u dp[i1][i2][i3] const int N = 51; LL dp[N][N][N]; int p1[26][N], p2[26][N], p3[26][N]; class CountingCommonSubsequences { public: long long countCommonSubsequences(string s1, string s2, string s3){ int n1 = SZ(s1), n2 = SZ(s2), n3 = SZ(s3); RST(dp); REP(c, 26){ p1[n1] = n1, p2[n2] = n2, p3[n3] = n3; DWN(i1, n1, 0) p1[i1] = s1[i1] == 'a' + c ? i1 : p1[i1+1]; DWN(i2, n2, 0) p2[i2] = s2[i2] == 'a' + c ? i2 : p2[i2+1]; DWN(i3, n3, 0) p3[i3] = s3[i3] == 'a' + c ? i3 : p3[i3+1]; dp[p1[0]][p2[0]][p3[0]] = 1; } LL res = 0; REP_3(i1, i2, i3, n1, n2, n3) if (u){ res += u; REP(c, 26) dp[p1[i1+1]][p2[i2+1]][p3[i3+1]] += u; } return res; } };
----------------------------
公共递增子序列
题
----------------------------
----------------------------
----------------------------
----------------------------
递增子序列
----------------------------
最长递增子序列
暂时缺题
----------------------------
----------------------------
----------------------------
递增子序列数
HDU 1423 Greatest Common Increasing Subsequence
----------------------------
----------------------------
----------------------------
最长条件子序列
----------------------------
最长差值子序列
----------------------------
给出一个正整数组成的序列,求他的最大值与最小值差不小于m不大于k的最长子序列。
----------------------------
代码待调整
const int maxn=110000; int a[maxn]; typedef pair<int,int> PII; deque<PII>q1,q2; int main() { int n,m,k; int ans,now; while (~scanf("%d%d%d",&n,&m,&k)) { ans=0; now=0; q1.clear(); q2.clear(); REP(i,n) { scanf("%d",&a[i]); while (!q1.empty()&&q1.back().first<=a[i]) q1.pop_back(); q1.push_back(mp(a[i],i)); while (!q2.empty()&&q2.back().first>=a[i]) q2.pop_back(); q2.push_back(mp(a[i],i)); while (!q1.empty()&&!q2.empty()&&q1.front().first-q2.front().first>k) { if (q1.front().second<q2.front().second) { now=q1.front().second+1; q1.pop_front(); } else { now=q2.front().second+1; q2.pop_front(); } } if (!q1.empty()&&!q2.empty()&&q1.front().first-q2.front().first>=m) ans=max(ans,i-now+1); } printf("%d\n",ans); } return 0; }
----------------------------
----------------------------