涉及一个序列的

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=3e7+10;
const int INF=0x3fffffff;
typedef long long LL;

char a[maxn],s[maxn];
int d[maxn];
void get_d(char *s,int n){
	d[1]=1;
	for(int i=2,l,r=1;i<=n;i++){
		if(i<=r) d[i]=min(d[r-i+l],r-i+1);
		while(s[i-d[i]]==s[i+d[i]]) d[i]++;
		if(i+d[i]-1>r) {
			l=i-d[i]+1;
			r=i+d[i]-1;
		}
	}
} 

int main(){
    scanf("%s",a+1);
    int n=strlen(a+1);
    int k=0;
    s[0]='$';
    s[++k]='#';
    for(int i=1;i<=n;i++){
    	s[++k]=a[i];s[++k]='#';
	}
	n=k;
	get_d(s,n);
	int ans=0;
	for(int i=1;i<=n;i++) ans=max(ans,d[i]);
	printf("%d",ans-1);
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;
}

  

 posted on 2020-03-21 21:12  shirlybabyyy  阅读(235)  评论(0编辑  收藏  举报