动态规划小结

最长上升子序列

代码【O(n*n)】
for(int i=1;i<=n;i++)
	scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)dp[i]=1;
	for(int i=1;i<=n;i++){
		for(int j=1;j<i;j++){
			if(a[j]<a[i])dp[i]=max(dp[i],dp[j]+1);//严格上升
			//if(a[j]<=a[i])dp[i]=max(dp[i],dp[j]+1);非严格
		}
	} 
	int ans=0;
	for(int i=1;i<=n;i++)ans=max(ans,dp[i]);
优化【O(nlogn)】
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=1e5+6;
#define ll long long
int d[maxn];
int main(){
	int n,x;
	cin>>n;
	int cnt=0;
	for(int i=0;i<n;i++){
		cin>>x;
		if(x>d[cnt])d[++cnt]=x;
		else{
			int pi=upper_bound(d+1,d+1+cnt,x)-d;
			d[pi]=x;
		}
	}
	cout<<cnt<<"\n";
}

d[i]:当前由数组中构成的lis长为i的最小结尾数据。

性质

由数字1到n的排列使得最长上升子序列长度为n-1共有(n-1)*(n-1)种。

最长公共子序列(LCS)

基本知识
只求长度
串s和串t:
dp[n][m]:s的n长前缀和t的m长前缀的最长公共子序列长度。 

1.if(i==0||j==0)dp[i][j]=0;
2.if(s[i-1]==t[j-1])dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);
3.if(s[i-1]!=t[j-1])dp[i][j]=max(dp[i][j],max(dp[i-1][j],dp[i][j-1]));
输出一个最大LCS串
#include<cstdio>
#include<iostream>
#include<string>
#include<algorithm>
#include<stack>
using namespace std;
const int maxn=3e3;
string s,t;
int ps[maxn],pt[maxn];
int dp[maxn][maxn];
struct Node{
	int ps,pt;
}node[maxn][maxn];
stack<char>st;
int main(){
	cin>>s>>t;
	int ls,lt;
	ls=s.size();
	lt=t.size();
	int LEN=max(ls,lt);
	for(int i=0;i<=LEN;i++){
		dp[i][0]=dp[0][i]=0;
	}
	for(int i=1;i<=ls;i++){
		for(int j=1;j<=lt;j++){
			if(s[i-1]==t[j-1]){
				if(dp[i][j]<dp[i-1][j-1]+1){
					dp[i][j]=dp[i-1][j-1]+1;
					node[i][j].ps=i-1;
					node[i][j].pt=j-1;
				}
			}else{
				if(dp[i][j]<dp[i-1][j]){
					dp[i][j]=dp[i-1][j];
					node[i][j]=node[i-1][j];
				}
				if(dp[i][j]<dp[i][j-1]){
					dp[i][j]=dp[i][j-1];
					node[i][j]=node[i][j-1];
				}
			}			
		}
	}
	int pi,pj;
	pi=ls;pj=lt; 
	int cnt=0;
	while(cnt<dp[ls][lt]){
		cnt++;
		st.push(s[node[pi][pj].ps]);
		Node tmp=node[pi][pj];
		pi=tmp.ps;
		pj=tmp.pt;
	}
	while(!st.empty()){
		cout<<st.top();
		st.pop();
	}
} 
优化
dp[i][j]的值只和dp[i-1][j-1],dp[i-1][j],dp[i][j-1]有关。
现删去一维,保留第二维。当对dp[j]进行讨论时,即dp[j]未赋值之前,那么dp[j]其实是dp[i-1][j],而dp[j-1]是dp[i][j-1],这时候只少了dp[i-1][j-1],其实就是未更新的dp[j-1],所以在计算dp[j-1]之前用last记录一下其值即可。
空间
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=1e5+6;
#define ll long long
int dp[maxn];
int main(){
	string s,t;
	cin>>s>>t;
	int ls,lt;
	ls=s.size();
	lt=t.size();
	for(int i=1;i<=ls;i++){
		int last=0;
		for(int j=1;j<=lt;j++){
			int tmp=dp[j];
			if(s[i-1]==t[j-1]){
				dp[j]=max(dp[j],last+1);
			}else{
				dp[j]=max(dp[j],dp[j-1]);
			}
			last=tmp;
		}
	}
	cout<<dp[lt]<<"\n";
}
时间
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=1e5+6;
#define ll long long
vector<int>vc[maxn];
int s[maxn],t[maxn];
int d[maxn];
int main(){
	int n;
	cin>>n;
	for(int i=0;i<n;i++)scanf("%d",&s[i]);
	for(int i=0;i<n;i++)scanf("%d",&t[i]);
	for(int i=n-1;i>=0;i--){
		vc[t[i]].push_back(i+1);
		//记录t串中每一个字符出现过的位置,并降序储存。 
	}
	//以下为优化了的LIS 
	int pos=0;
	for(int i=0;i<n;i++){
		int zf=s[i];
		for(int j=0;j<vc[zf].size();j++){
			int x=vc[zf][j];
			if(x>d[pos])d[++pos]=x;
			else{
				int pi=upper_bound(d+1,d+1+pos,x)-d;
				d[pi]=x;
			}
		}
	}
	cout<<pos<<"\n";
}

当串中每个字符只出现一次的话:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=1e5+6;
#define ll long long
int pos[maxn];
int d[maxn];
int main(){
	int n,x;
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>x;
		pos[x]=i+1;
	}
	int cnt=0;
	for(int i=0;i<n;i++){
		cin>>x;
		x=pos[x];
		if(x>d[cnt])d[++cnt]=x;
		else{
			int pi=upper_bound(d+1,d+1+cnt,x)-d;
			d[pi]=x;
		}
	}
	cout<<cnt<<"\n";
}

最长公共子串

最大字段和

基础

即找一个连续段,其和最大。原序列存在数组a中。
d[i]:下标i结尾的连续段的最大和。
d[i]=max(d[i-1]+a[i],a[i])

	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	int ans,sum;
	ans=-inf;
	sum=0;
	for(int i=1;i<=n;i++){
		sum=max(sum+a[i],a[i]);
		ans=max(sum,ans);
	}
二维+动态修改

线段树维护二维最大字段和
hdu 6638:http://acm.hdu.edu.cn/showproblem.php?pid=6638

#define ll long long
const int maxn=3000;
ll mx[maxn*4],pre[maxn*4],last[maxn*4],sum[maxn*4];
void pushup(int x){
	sum[x]=sum[2*x]+sum[2*x+1];
	mx[x]=max(max(mx[2*x],mx[2*x+1]),last[2*x]+pre[2*x+1]);
	pre[x]=max(pre[2*x],sum[2*x]+pre[2*x+1]);
	last[x]=max(last[2*x+1],sum[2*x+1]+last[2*x]);
}
void add(int x,int l,int r,int pos,ll val){
	if(l==r){
		mx[x]+=val;
		sum[x]=pre[x]=last[x]=mx[x];
		return;
	}
	int mid=(l+r)>>1;
	if(pos<=mid)add(2*x,l,mid,pos,val);
	else add(2*x+1,mid+1,r,pos,val);
	pushup(x);
}
void init(int x,int l,int r){
	mx[x]=pre[x]=last[x]=sum[x]=0;
	if(l==r){
		return;
	}
	int mid=(l+r)>>1;
	init(2*x,l,mid);
	init(2*x+1,mid+1,r);
}
struct Nt{
	int x,y;
	ll w;
	bool operator <(const Nt&o)const{
		if(y==o.y)return x<o.x;
		return y<o.y;
	}
}nt[maxn];
int NUM_x[maxn],NUM_y[maxn];
void work(){
	int n;
	scanf("%d",&n);
	int xx,yy;
	ll ww;
	for(int i=1;i<=n;i++){
		scanf("%d%d%lld",&xx,&yy,&ww);
		NUM_x[i]=xx;NUM_y[i]=yy;
		nt[i]=(Nt){xx,yy,ww}; 
	}
	sort(NUM_x+1,NUM_x+1+n);
	int lx=unique(NUM_x+1,NUM_x+1+n)-(NUM_x+1);
	sort(NUM_y+1,NUM_y+1+n);
	int ly=unique(NUM_y+1,NUM_y+1+n)-(NUM_y+1);
	for(int i=1;i<=n;i++){
		nt[i].x=lower_bound(NUM_x+1,NUM_x+1+lx,nt[i].x)-(NUM_x);
		nt[i].y=lower_bound(NUM_y+1,NUM_y+1+ly,nt[i].y)-(NUM_y);
	}
	sort(nt+1,nt+1+n);
	int cx=1;
	ll ans=0;
	for(int i=1;i<=ly;i++){
		init(1,1,lx);
		int poi=cx;
		for(int j=i;j<=ly;j++){
			while(poi<=n&&nt[poi].y==j){
				add(1,1,lx,nt[poi].x,nt[poi].w);
				poi++;
			}
			if(i==j)cx=poi;
			ans=max(ans,mx[1]);
		}
	}
	printf("%lld\n",ans);
}
int main(){
	int T;
	cin>>T;
	while(T--){
		work();
	}
} 

最大m字段和

在n元素数组中取m个连续段,求其和的最大值。【这m个连续段互不重合即可】
当m==1时,即为最大字段和。

dp [i] [j]:前j个元素分成i段的答案
dp [i] [j] = max(dp [i] [j] , dp [i] [j-1] + a [j] ),i<=j-1【相当于i != j,这里wa了好几发】
dp [i] [j] = max(dp [i] [j] , dp [i-1] [k] + a [j] ),i-1<=k<=j-1

题目http://acm.hdu.edu.cn/showproblem.php?pid=1024
用到了滚动数组

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1e6+6;
const int inf=0x3f3f3f3f;
int a[maxn];
int dp[maxn][3];
int main(){
	int m,n;
	while(~scanf("%d%d",&m,&n)){
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
		}
		memset(dp,0,sizeof(dp));
		int ans=-inf;
		for(int j=1;j<=m;j++){
			int mx=dp[j-1][(j+1)%2];
			for(int i=j;i<=n;i++){
				int tmp=-inf;//tmp=0会报错
				if(i-1>=j)
					tmp=max(dp[i-1][j%2]+a[i],tmp);
				tmp=max(tmp,mx+a[i]);
				dp[i][j%2]=tmp;
				mx=max(mx,dp[i][(j+1)%2]);
			}
		}
		for(int i=m;i<=n;i++){
			ans=max(ans,dp[i][m%2]);
		}
		printf("%d\n",ans);
	}
} 
//dp[i][j]=max(dp[i][j],dp[i-1][j]+a[i])    i-1>=j
//dp[i][j]=max(dp[i][j],dp[k][j-1]+a[i])    j-1<=k<i-1
const int maxn=1e6+6;
const int inf=0x3f3f3f3f;
int a[maxn];
int dp[3][maxn];
void work(int n,int m){
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    memset(dp,0,sizeof(dp));
    int mx;
    for(int i=1;i<=m;i++){
        mx=dp[(i+1)%2][i-1];
        for(int j=i;j<=n;j++){//n亦可变成n-m+i,减少不必要次数
        	if(i==j)
			dp[i%2][j]=mx+a[j];
        	else 
            dp[i%2][j]=max(mx,dp[i%2][j-1])+a[j];
            mx=max(mx,dp[(i+1)%2][j]);
        }
    }
    int ans=-inf;
    for(int i=m;i<=n;i++){
        ans=max(ans,dp[m%2][i]);
    }
    printf("%d\n",ans);
/*	或者:
	for(int i=1;i<=m;i++){
        mx=-inf;
        for(int j=i;j<=n;j++){
            dp[0][j]=max(dp[1][j-1],dp[0][j-1])+a[j];
            dp[1][j-1]=mx;
            mx=max(mx,dp[0][j]);
        }
    }
    printf("%d\n",mx);
*/
}
int main(){
    int n,m;
    while(~scanf("%d%d",&m,&n)){
        work(n,m);
    }
}

最小字段和

各元素求相反数,再求一个最大字段和即可,答案即是所求的相反数。

最小正字段和

求一个连续段,只要和为正即可,然后在这些连续段中,找出和最小的那个。
方法:计算前缀和后,将之升序排序,于是每相邻的两个前缀和之差最小(若位置ok,那么差值即为该段之和。需要注意若前缀和大于0本身也是答案之一)。
题目:https://vjudge.net/problem/51Nod-1065
具体看代码:

#define ll long long
const int maxn=6e5+66;
const int inf=0x3f3f3f3f;
struct NT{
	ll x;
	int id;
	bool operator <(const NT&o)const{
		if(x==o.x)return id<o.id;
		return x<o.x;
	}
}nt[maxn]; 
int main(){
	int n;
	scanf("%d",&n);
	int x;
	nt[0].x=0;
	nt[0].id=0;
	for(int i=1;i<=n;i++){
		scanf("%d",&x);
		nt[i]=(NT){x+nt[i-1].x,i}; 
	}
	ll ans=inf;
	sort(nt+1,nt+1+n);
	for(int i=1;i<=n;i++){
		if(nt[i].x>0)ans=min(ans,nt[i].x);//这里别忘了
		if(nt[i].x>nt[i-1].x&&nt[i].id>nt[i-1].id){
			ans=min(ans,nt[i].x-nt[i-1].x);
		}
	}
	printf("%lld\n",ans);
}

最大子矩阵和

类似二维的最大字段和。
在一个大矩阵中找到一个子矩阵,其和最大。
a[i][j]:1~i行的j列的和。
枚举上下边界i、j,然后将每一列的i行到j行的值加起来,于是二维变一维,剩下的就是求最大字段和。

	int x;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			scanf("%d",&x);
			a[i][j]=a[i-1][j]+x;
		}
	}
	int ans=-inf;
	for(int i=1;i<=n;i++){//上界 
		for(int j=i;j<=n;j++){//下界 
			int sum=0;
			for(int k=1;k<=m;k++){//夹在上下界之间的列
				sum=max(sum+a[j][k]-a[i-1][k],a[j][k]-a[i-1][k]);
				ans=max(ans,sum); 
			}
		} 
	}

复杂度为 n3

01背包:

每个物品只有一件,各自有各自重量以及价值。一个背包可装一定重量,问如何选择物品,使得背包装的价值最多且不超重。

//物品n个,背包最多装m重。
//f[i][j]表示前i件物品,总重量不超过j的最大价值
for(int i=1;i<=n;i++)
    for(int j=m;j>0;j--){
        if(w[i]<=j)
           f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i]);
        else f[i][j]=f[i-1][j];
    }
//滚掉一维后:
//f[j]表示重量不超过j公斤的最大价值 
for(int i=1;i<=n;i++){       
      for(int j=m;j>=w[i];j--){//倒着的!
          f[j]=max(f[j],f[j-w[i]]+v[i]);  
      }
}
    

完全背包:

基本条件同01背包,只是完全背包的物品有无限个,即可以一个物品取无限次。

for(int i=1;i<=n;i++){
    for(int  j = w[i];j <= m;j++){//正着来的,与01背包不同之处
        f[j] = max(f[j], f[j-w[i]]+v[i]);
    }
}

多重背包:

基本条件同01背包,只是多重背包的物品个数有限,即可以一个物品只可取有限次。

    for(int i=1;i<=n;i++)
    for(int j=m;j>=w[i];j--)//倒着来的,同01背包
    for(int k=0;k<=c[i];k++){//取物品i取k次
        if(j-k*w[i]<0)break;
        f[j] = max(f[j], f[j-k*w[i]]+k*v[i]);
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值