涉及一个序列的
1、最长递增子序列(最长不下降子序列)
合唱队形:从两边求最长不下降子序列,然后遍历每一个点,分析它的两边的情况,选择最大的。最少拦截系统
第一种算法:两个数组,如果需要记录路径就是三个
int n; int a[201],c[201];
//本身,前驱 int b[201];
//得到的结果序列 //第一种做法,复杂度O(N^2) int main(){ cin>>n; for(int i=1;i<=n;i++){ cin>>a[i];b[i]=1;c[i]=0; } int l,k; for(int i=n-1;i>=1;i--){ l=0,k=0; for(int j=i+1;j<=n;j++){ if(a[j]>a[i]&&b[j]>l) { //选择大于它的且现在长度最大的 l=b[j]; k=j; } } if(l>0){ b[i]=l+1; c[i]=k; } } k=1; for(int i=1;i<=n;i++) if(b[i]>b[k]) k=i; cout<<"max="<<b[k]<<endl; //路径的输出 while(k!=0){ cout<<" "<<a[k]; k=c[k]; } return 0; }
第二种算法
复杂度O(Nlog2N)
两种操作,先与已经选择好的序列最后一位进行比较,如果大于最后一位,那么就直接放进去,len++;
如果比最后一位小,那么就在已经选择好的序列里面找到比它大的第一个数,然后替换,因为这样能够保障插入更多的数
//利用有序队列优化,f[i]=max(f[j]+1),j<i且a[j]<=a[i],而且f[j]要尽可能地大!!!
int n;
int a[maxn]; //本事
int d[maxn]; //得到的结果序列
int pre[maxn]; //用来输出路径
int main(){
cin>>n;
int len=1;
for(int i=1;i<=n;i++) cin>>a[i];
d[1]=a[1];pre[1]=1;
for(int i=2;i<=n;i++){
if(d[len]<=a[i]) {
d[++len]=a[i];pre[i]=len;
}
else{
int j=upper_bound(d+1,d+1+len,a[i])-d; //返回第一个大于a[i]的坐标
d[j]=a[i]; //否则就找到位置替换掉
pre[i]=j;
}
}
stack<int> st;//用栈来存储这个路径
for(int i=n,j=len;i>=1;i--){
if(pre[i]==j){
st.push(a[i]);
--j;
}
if(j==0) break;
}
cout<<len<<endl;
while(!st.empty()){
cout<<st.top()<<" ";
st.pop();
}
return 0;
}
2、最长回文子串
动态规划的做法:最简单O(N^2)
//最长回文子串
const int maxn=1001;
char a1[maxn];
int dp[maxn][maxn];//其实这是个类似于bool数组的作用,只是用来判断是不是回文串
int findmaxhuiwen(){
gets(a1);
int ans=1;
int len=strlen(a1);
for(int i=0;i<len;i++){
dp[i][i]=1;
if(i+1<len){
if(a1[i]==a1[i+1]) {
dp[i][i+1]=1;
ans=2;
}
}
}//初始化
for(int l=3;l<=len;l++){ //以长度来循环
for(int i=0;i+l-1<len;i++){ //左边端点
int j=i+l-1; //右边端点
//里面不需要循环了,只有判断一次就够了
if(a1[i]==a1[j]&&dp[i+1][j-1]) {
dp[i][j]=1;
ans=l; //l为长度
}
}
}
return ans;
}
字符串hash+二分的算法(O(nlogn))
//字符串hash+二分
//写写思路:首先可以先到把字符串反转,计算两个字符串的hash值,然后进行比较,看最大的半径(以分界点为中心)在哪里,但是如果单纯枚举半径
//就会超时,所以二分回文半径
//但是有需要区分:回文串长度是奇数还是偶数(因为区间不同,所以分别计算:回文长度为偶数和回文长度为奇数的情况,计算最大的回文半径
//《算法笔记》第45页
long long pw[maxn],h1[maxn],h2[maxn];
void inti(int len){
pw[0]=1;
for(int i=1;i<=len;i++) pw[i]=(P*pw[i-1])%MOD; //计算每一位进制数
}
void gethash(string &s,long long pe[]){ //计算字符串的hash数组
pe[0]=s[0]-'a';
for(int i=1;i<s.length();i++){
pe[i]=(pe[i-1]*P+s[i]-'a')%MOD;
}
}
long long jssubstrhash(long long h[],int i,int j){ //计算子串的hash值
if(i==0) return h[j]; //如果以0开头,就直接返回(已经计算过了)
//公式:h[i..j]=((h[j]-h[i-1]*p^(j-i+1))%MOD+MOD)%MOD
else return ((h[j]-h[i-1]*pw[j-i+1])%MOD+MOD)%MOD;
}
//回文半径上下限为l,r,分界点为i,字符串串长为len,判断是不是整数iseven
int getsearch(int l,int r,int len,int i,int iseven){
while(l<r){
int mid=(l+r)/2;
int h1l=i-mid+iseven,h1r=i;
int h2l=len-1-(i+mid),h2r=len-1-(i+iseven);
int hash1=jssubstrhash(h1,h1l,h1r);
int hash2=jssubstrhash(h2,h2l,h2r);
if(hash1!=hash2) r=mid; //不匹配:回文半径太大了,减小一点
else l=mid+1; //相等的话说明还可能可以扩充
}
return l-1; //返回最大回文半径
}
string str;
void findmaxbanjing(){
getline(cin,str);
inti(str.length());
gethash(str,h1); //计算原来的串的hash
reverse(str.begin(),str.end()); //反转这个串
gethash(str,h2);
//区分奇数回文长度和偶数回文长度
int ans=0;
for(int i=0;i<str.length();i++){
int maxlen=min(i,(int)str.length()-1-i)+1; //最大的回文半径(上限)--左右长度最小值+1
//注意str.length()前面要加(int),不要会出现错误!!
int k=getsearch(0,maxlen,str.length(),i,0); //iseven=0;
ans=max(ans,2*k+1);
}
//偶数
for(int i=0;i<str.length();i++){
int maxlen=min(i+1,(int)str.length()-1-i)+1; //注意最大的回文长度 (左长为i+1)!!!不知道why
//注意str.length()前面要加(int),不要会出现错误!!
int k=getsearch(0,maxlen,str.length(),i,1);
ans=max(ans,2*k);
}
cout<<"max huiwen substr leghth is "<<ans<<endl;
}
int main(){
findmaxbanjing();
return 0;
}
最优秀的Manacher算法(马拉车算法)
详解:https://www.cnblogs.com/z360/p/6375514.html
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1000010;
const int INF=0x3fffffff;
typedef long long LL;
//马拉车算法,最优秀的计算最长回文子串的算法,O(N)的复杂度
//详解:https://www.cnblogs.com/z360/p/6375514.html
/*
将长度为奇数的回文串和长度为偶数的回文串一起考虑,具体做法是,在原字符串的每个相邻两个字符中间插入一个分隔符,
同时在首尾也要添加一个分隔符,分隔符的要求是不在原串中出现,一般情况下可以用#号
*/
char str[maxn];//原始串
char tmp[maxn<<1]; //转换后的字符串
int Len[maxn<<1];//Len[i]表示以字符T[i]为中心的最长回文字串的最右字符到T[i]的长度,比如以T[i]为中心的最长回文字串是T[l,r],那么Len[i]=r-i+1
//转换原始串
int inti(char *st){
int len=strlen(st);
tmp[0]='@'; //字符串开头增加一个特殊字符,防止越界
for(int i=1;i<=2*len;i+=2){
tmp[i]='#';
tmp[i+1]=st[i/2];
}
tmp[2*len+1]='#';
tmp[2*len+2]='$'; //字符串结尾加一个字符,防止越界
tmp[2*len+3]=0;
return 2*len+1; //返回转换字符串的长度
}
//马拉车算法
int manacher(char *st,int len){
int mx=0,ans=0,po=0; //mx即为当前计算回文串最右边字符的最大值
for(int i=1;i<=len;i++){
if(mx>i) Len[i]=min(mx-i,Len[2*po-i]); //在Len[j]和mx-i中取个小
else Len[i]=1; //如果i>=mx,要从头开始匹配
while(st[i-Len[i]]==st[i+Len[i]]) Len[i]++;
if(Len[i]+i>mx){ //若新计算的回文串右端点位置大于mx,要更新po和mx的值
mx=Len[i]+i;
po=i;
}
ans=max(ans,Len[i]);
}
return ans-1; //返回Len[i]中的最大值-1即为原串的最长回文子串额长度
}
int main(){
scanf("%s",str);
int l=inti(str);
printf("%d
",manacher(tmp,l));
return 0;
}
3、最大连续子序列和
int a2[maxn],dp1[maxn]; //dp[i]表示以i为结尾的最大连续子序列和
//最大连续子序列和
int findmaxsum(){
int n;
cin>>n;
for(int i=0;i<n;i++){
cin>>a2[i];
}
dp1[0]=a2[0];
for(int i=1;i<n;i++){
dp1[i]=max(dp1[i-1]+a2[i],a2[i]); //前一个为负数
}
int maxx=-99999;
for(int i=0;i<n;i++) maxx=max(maxx,dp1[i]);
return maxx;
}
1305:Maximum sum
找出两个不重合连续子段,使得两子段中所有数字的和最大
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#define INF 10000000
#define N 11
using namespace std;
int a[50001],minn[50001],maxx[50001];
int main()
{
int t,n;
cin>>t;
while(t--){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
minn[1]=a[1];
for(int i=2;i<=n;i++){
if(minn[i-1]<0) minn[i]=a[i];
else minn[i]=minn[i-1]+a[i];
}
for(int i=2;i<=n;i++) minn[i]=max(minn[i],minn[i-1]);
maxx[n]=a[n];
for(int i=n-1;i>=1;i--){
if(maxx[i+1]<0) maxx[i]=a[i];
else maxx[i]=maxx[i+1]+a[i];
}
for(int i=n-1;i>=1;i--) maxx[i]=max(maxx[i+1],maxx[i]);
int ans=-INF;
for(int i=1;i<=n;i++) ans=max(ans,minn[i-1]+maxx[i]);
cout<<ans<<endl;
}
return 0;
}
涉及两个序列的
1、最长公共子序列 LCS
O(NM)
string s1,s2;
int dp[maxn][maxn];
int lcs(){
memset(dp,0,sizeof(dp));
for(int i=1;i<s1.length();i++){
for(int j=1;j<s2.length();j++){
if(s1[i-1]==s2[j-1]) dp[i][j]=dp[i-1][j-1]+1;
else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
return dp[s1.length()-1][s2.length()-1];
}
int main(){
cin>>s1>>s2;
cout<<lcs()<<endl;
return 0;
}
2、给定一个字符串,最少插入多少个字符,才能使该字符串变为回文串
设原序列为X,逆序列为Y,那么最少插入的数字为=X的长度-X与Y的最长公共子序列的长度(LCS)
更重要的是对这个问题的空间的压缩处理,利用滚动数组,求LCS的状态转移方程为d[i][j]=d[i-1][j]+d[i][j-1]看看依赖情况
可以用滚动数组优化为:d[i%2][j]=d[(i-1)%2][j]+d[i%2][j-1]
(在POJ上编译错误不晓得为什么)
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
typedef long long LL;
int dp[2][5005];
int main(){
string s1,s2;
int n,i,j;
while(cin>>n){
cin>>s1;
s2=s1;
reverse(s1.begin(),s1.end());
memset(dp,0,sizeof(dp));
for(i=1;i<=n;i++){
for(j=1;j<=n;j++){
dp[i%2][j]=max(dp[(i-1)%2][j],dp[i%2][j-1]);
if(s1[i-1]==s2[j-1]){
int tmp=dp[(i-1)%2][j-1]+1;
dp[i%2][j]=max(dp[i%2][j],tmp);
}
}
}
cout<<n-dp[n%2][n]<<endl;
}
return 0;
}
3、最长公共上升子序列
int a[501],b[501];
int t[501][501]={0}; //用来记录的
int s[501]; //临时存储
int n,m;
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
cin>>m;
for(int i=1;i<=m;i++) cin>>b[i];
// a[0]=b[0]=-999999;//预处理边界值
//以b为大循环,遍历a与之比较
for(int i=1;i<=m;i++){
memset(s,0,sizeof(s));//初始化s
for(int j=1;j<=n;j++){
if(b[i]>a[j]&&s[0]<t[j][0]){//符合局部上升且之前存储过公共上升子序列
memcpy(s,t[j],sizeof(t[j])); //就保存到s中(调出之前存储的公共上升子序列)
}
if(b[i]==a[j]){
memcpy(t[j],s,sizeof(s));//将s复制给t,存储当前情况下子序列
t[j][++t[j][0]]=a[j];//接上子序列并计算长度+1
}
}
}
int ans=0;//找出最大长度
for(int i=1;i<=n;i++) if(ans<t[i][0]) ans=t[i][0];
cout<<ans<<endl;
for(int i=1;i<=n;i++) {
if(t[i][0]==ans){
for(int j=1;j<=ans;j++) cout<<t[i][j]<<" ";
break;
}
}
return 0;
}