回文子序列
----------------------------
最长回文子序列
题目略
----------------------------
这是回文子序列的基础,直接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;
}
----------------------------
----------------------------