zoukankan      html  css  js  c++  java
  • dp序列问题

    涉及一个序列的

    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;
    }
    

      

  • 相关阅读:
    laravel框架——保存用户登陆信息(session)
    laravel框架——增删改查
    laravel框架——表单验证
    laravel框架——上传、下载文件
    Forms & HTML 组件
    phantomJS+Python 隐形浏览器
    Python idle中lxml 解析HTML时中文乱码解决
    python 根据字符串语句进行操作再造函数(evec和eval方法)
    python通过LXML库读取xml命名空间
    Python通过lxml库遍历xml通过xpath查询(标签,属性名称,属性值,标签对属性)
  • 原文地址:https://www.cnblogs.com/shirlybaby/p/12542271.html
Copyright © 2011-2022 走看看