涉及一个序列的
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; }