洛谷 【动态规划2】线性状态动态规划

题单链接

P1020 导弹拦截【最长上升子序列LIS】

  • 分别求最长不上升子序列、最长上升子序列

O ( n 2 ) O(n^2) O(n2)算法

#include<iostream>
#include<algorithm>
using namespace std;
int arr[100010],n,dp[100010],lds,cnt,f[100010],lis;
int ldsfind(int x){
	int ans=0;
	for(int i=1;i<x;i++){
		if(arr[i]>=arr[x]&&dp[i]>ans) ans=dp[i];
	}
	return ans;
}
int lisfind(int x){
	int ans=0;
	for(int i=1;i<x;i++){
		if(arr[i]<arr[x]&&f[i]>ans) ans=f[i];
	}
	return ans;
}
int main(){
	while(cin>>arr[++n]);n--;
	dp[1]=1;f[1]=1;
	for(int i=2;i<=n;i++){
		dp[i]=ldsfind(i)+1;
		f[i]=lisfind(i)+1;
	}
	for(int i=1;i<=n;i++){
		lds=max(lds,dp[i]);
		lis=max(lis,f[i]);
	}		
	cout<<lds<<endl;
	cout<<lis<<endl;
} 

O ( n l o g n ) O(nlogn) O(nlogn)算法

  • x [ k ] x[k] x[k]为长度为 k k k的上升子序列的最末元素,若有多个长度为 k k k的上升子序列,则记录最小的那个最末元素
  • x [ ∗ ] x[*] x[]中元素是单调递增的,对 a [ i ] a[i] a[i]:若 a [ i ] > x [ l e n ] a[i]>x[len] a[i]>x[len],那么 x [ + + l e n ] = a [ i ] x[++len] = a[i] x[++len]=a[i];否则,从 x [ 1 ] x[1] x[1] x [ l e n ] x[len] x[len]中找到一个 j j j,满足 x [ j − 1 ] < a [ i ] < x [ j ] x[j-1]<a[i]<x[j] x[j1]<a[i]<x[j],根据 x [ ∗ ] x[*] x[]的定义,我们需要更新长度为j的上升子序列的最末元素(使之为最小的)即 x [ j ] = a [ i ] x[j] = a[i] x[j]=a[i];
#include<iostream>
#include<algorithm>
using namespace std;
int arr[100010],len=1,x[100010],n;
int find(int i){
	int l=1,r=len,ans=-1;
	while(l<=r){
		int mid=(l+r)>>1;
		if(arr[i]>x[mid]){
			ans=mid;
			r=mid-1;
		} 
		else l=mid+1; 
	}
	return ans;	
}
int find2(int i){
	int l=1,r=len,ans=-1;
	while(l<=r){
		int mid=(l+r)>>1;
		if(arr[i]<=x[mid]){
			ans=mid;
			r=mid-1;
		} 
		else l=mid+1; 
	}
	return ans;	
}
int main(){
	while(cin>>arr[++n]);n--;
	x[1]=arr[1];
	for(int i=2;i<=n;i++){
		if(arr[i]<=x[len]) x[++len]=arr[i];
		else{
			x[find(i)]=arr[i];
		}
	}
	cout<<len<<endl;
	len=1;
	x[1]=arr[1];
	for(int i=2;i<=n;i++){
		if(arr[i]>x[len]) x[++len]=arr[i];
		else{
			x[find2(i)]=arr[i];
		}
	}
	cout<<len<<endl;	
} 

参考博客

P1439 【模板】最长公共子序列【LCS】

O ( n 2 ) O(n^2) O(n2)算法

#include<iostream>
#include<algorithm>
using namespace std;
int n,a[10010],b[10010],f[10010][10010];
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=n;i++){
		cin>>b[i];
	}
	f[1][0]=f[0][1]=f[0][0]=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(a[i]==b[j]) f[i][j]=f[i-1][j-1]+1;
			else{
				f[i][j]=max(f[i-1][j],f[i][j-1]);
			}
		}
	}
	cout<<f[n][n]<<endl;	
} 

O ( n l o g n ) O(nlogn) O(nlogn)算法

  • 对A数组利用 m p [ x ] = i mp[x]=i mp[x]=i将A变成一个递增的序列
  • 对B数组利用A中的映射规则,转化为求B的最长递增子序列问题
#include<iostream>
#include<algorithm>
using namespace std;
int a[100010],b[100010],mp[100010],len=1,x[100010],n;
int find(int i){
	int l=1,r=len,ans=-1;
	while(l<=r){
		int mid=(l+r)>>1;
		if(mp[b[i]]<=x[mid]){
			ans=mid;
			r=mid-1;
		} 
		else l=mid+1; 
	}
	return ans;	
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];mp[a[i]]=i;
	} 
	for(int i=1;i<=n;i++){
		cin>>b[i];
	}
	x[1]=mp[b[1]];
	for(int i=2;i<=n;i++){
		if(mp[b[i]]>x[len]) x[++len]=mp[b[i]];
		else{
			x[find(i)]=mp[b[i]];
		}
	}
	cout<<len<<endl;	
} 

P1280 尼克的任务

  • f [ i ] f[i] f[i]表示第 i i i个时刻到第 n n n个时刻的值,逆向更新
  • b [ i ] b[i] b[i]表示以第 i i i个时刻为起点的所有时间段的统计
  • f [ i ] = { f [ i + 1 ] + 1 , b [ i ] = = 0 m a x ( f [ i ] , f [ i + a [ c n t ] . t ] ) , b [ i ] ≠ 0 f[i]=\left\{ \begin{aligned} f[i+1]+1 ,b[i]==0 \\ max(f[i],f[i+a[cnt].t]),b[i]\neq 0 \end{aligned} \right. f[i]={f[i+1]+1,b[i]==0max(f[i],f[i+a[cnt].t]),b[i]=0
#include<iostream>
#include<algorithm>
int n,k;
using namespace std;
const int maxn=10010;
struct node{
	int p,t;
	bool operator<(const node& n) const{
		return p>n.p;
	}
}a[maxn]; 
int b[maxn],cnt=1,f[maxn];
int main(){
	cin>>n>>k;
	for(int i=1;i<=k;i++){
		cin>>a[i].p>>a[i].t;
		b[a[i].p]++;
	}
	sort(a+1,a+k+1); 
	for(int i=n;i>=1;i--){
		if(b[i]==0) f[i]=f[i+1]+1;
		else{
			for(int j=1;j<=b[i];j++){
				f[i]=max(f[i],f[i+a[cnt].t]);
				cnt++;
			}
		}
	}
	cout<<f[1]<<endl;
} 

P2758 编辑距离

  • f [ i ] [ j ] f[i][j] f[i][j]表示 A [ 1 − i ] A[1-i] A[1i] B [ 1 − j ] B[1-j] B[1j]的编辑距离
  • f [ i ] [ j ] = m a x { f [ i − 1 ] [ j ] + 1 , A 删 除 一 个 字 符 f [ i ] [ j − 1 ] + 1 , A 删 除 一 个 字 符 f [ i − 1 ] [ j − 1 ] + 1 , A 修 改 一 个 字 符 f[i][j]=max\left\{ \begin{aligned} f[i-1][j]+1,A删除一个字符 \\ f[i][j-1]+1,A删除一个字符 \\ f[i-1][j-1]+1,A修改一个字符 \end{aligned} \right. f[i][j]=maxf[i1][j]+1,Af[i][j1]+1,Af[i1][j1]+1,A
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
string A,B;
int f[2010][2010]; 
int main(){
	cin>>A>>B;A.insert(0," ");B.insert(0," ");
	for(int i=0;i<A.length();i++) f[i][0]=i;
	for(int j=0;j<B.length();j++) f[0][j]=j;
	for(int i=1;i<A.length();i++){
		for(int j=1;j<B.length();j++){
			f[i][j]=min(f[i-1][j]+1,min(f[i][j-1]+1,f[i-1][j-1]+(A[i]==B[j]?0:1)));
		}
	}
	cout<<f[A.length()-1][B.length()-1]<<endl;	 
} 

P1040 加分二叉树【区间DP】

  • f [ i ] [ j ] f[i][j] f[i][j]表示节点 i i i到节点 j j j的最大加分
  • 在区间 [ i , j ] [i,j] [i,j]中枚举树根的位置,状态转移方程为 f [ i ] [ j ] = m a x ( f [ i ] [ k − 1 ] ∗ f [ k + 1 ] [ j ] + f [ k ] [ k ] ) , k ∈ [ l , r ) f[i][j]=max(f[i][k-1]*f[k+1][j]+f[k][k]),k\in[l,r) f[i][j]=max(f[i][k1]f[k+1][j]+f[k][k])k[l,r)
  • r o o t [ i ] [ j ] root[i][j] root[i][j]记录从节点 i i i到节点 j j j取得最大加分时的子树的根
#include<iostream>
#include<algorithm>
typedef long long ll;
using namespace std;
int n,root[40][40];
ll f[40][40];
void preorder(ll l,ll r){
	if(l<=r){
		cout<<root[l][r]<<" ";
		preorder(l,root[l][r]-1);
		preorder(root[l][r]+1,r);
	}
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>f[i][i];
		f[i][i-1]=1;//子树不存在,返回1
		root[i][i]=i;
	}
	for(int len=1;len<=n;len++){
		for(int l=1;l+len-1<=n;l++){
			int r=l+len-1;
			for(int k=l;k<r;k++){
				if(f[l][r]<f[l][k-1]*f[k+1][r]+f[k][k]){
					f[l][r]=f[l][k-1]*f[k+1][r]+f[k][k];
					root[l][r]=k;
				} 
			}
		}
	}
	cout<<f[1][n]<<endl;	
	preorder(1,n);
} 

P4933 大师

  • f [ i ] [ k ] f[i][k] f[i][k]为以第 i i i个元素结尾的,公差为 k k k的等差子数列的个数
  • f [ i ] [ k ] = ∑ j < i f [ j ] [ k ] + 1 ( a [ i ] − a [ j ] = = k ) f[i][k]=\sum_{j<i}f[j][k]+1(a[i]-a[j]==k) f[i][k]=j<if[j][k]+1(a[i]a[j]==k).其中 + 1 +1 +1 a [ i ] a[i] a[i] a [ j ] a[j] a[j]单独构成等差数列的情况
  • 将枚举 i , j , k i,j,k i,j,k优化为仅枚举 i , j i,j i,j:利用 k = a [ i ] − a [ j ] k=a[i]-a[j] k=a[i]a[j]
  • 等差数列的公差可正可负,加一个足够大的数将公差全部变为正数
  • f [ i ] [ a [ i ] − a [ j ] + m a x h ] + = f [ j ] [ a [ i ] − a [ j ] + m a x h ] + 1 ( j < i ) f[i][a[i]-a[j]+maxh]+=f[j][a[i]-a[j]+maxh]+1(j<i) f[i][a[i]a[j]+maxh]+=f[j][a[i]a[j]+maxh]+1(j<i)
  • a n s = n + ∑ f [ i ] [ k ] ans=n+\sum f[i][k] ans=n+f[i][k],(n为单独一个数的情况), f [ i ] [ k ] f[i][k] f[i][k]可用上一条公式代换
#include<iostream>
#include<algorithm>
#include<cstring>
typedef long long ll;
using namespace std;
ll ans,mod=998244353,f[1010][40010];
int h[1010],tot,maxh=20010,n;
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>h[i];
	}
	for(int i=2;i<=n;i++){
		for(int j=1;j<i;j++){
			f[i][h[i]-h[j]+maxh]=(f[i][h[i]-h[j]+maxh]+f[j][h[i]-h[j]+maxh]+1)%mod;
			ans=(ans+f[j][h[i]-h[j]+maxh]+1)%mod;
		}
	} 
	ans=(ans+n)%mod;
	cout<<ans<<endl;
} 

P1077 摆花

#include<iostream>
#include<algorithm>
using namespace std;
int n,m,a[110],f[110][110],mod=1000007;
//f[i][j]用前i种花组成j盆的方案数 
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}	
	for(int i=0;i<=n;i++){		
		f[i][0]=1;
	}
	for(int i=1;i<=n;i++){
		for(int k=0;k<=a[i];k++){//第i种花选k盆 
			 for(int j=0;j<=m-k;j++){
			 	//从已装的j盆转移过来
				if(k==0&&j==0) continue;
				f[i][j+k]+=f[i-1][j];
				f[i][j+k]%=mod;			 	
			 }
		}
	}
	cout<<f[n][m]<<endl;	
} 

P1233 木棍加工

狄尔沃斯(Dilworth)定理:对于任意有限偏序集,其最大反链中元素的数目必等于最小链划分中链的数目;其最长链中元素的数目必等于其最小反链划分中反链的数目

  • 按照木棍的长度排序
  • 求宽度的最长上升子序列
#include<iostream>
#include<algorithm>
using namespace std;
int n,dp[5010],ans;
struct stick{
	int l,w;
	bool operator<(const stick& t)const{
		return l>t.l;
	}
}s[5010];
int main(){
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>s[i].l>>s[i].w;
	}
	sort(s,s+n);
	dp[0]=1;
	for(int i=1;i<n;i++){
		for(int j=0;j<i;j++){
			if(s[j].w<s[i].w){
				dp[i]=max(dp[i],dp[j]);	
			}			
		}
		dp[i]+=1;		
	}
	for(int i=0;i<n;i++){
		ans=max(ans,dp[i]);
	}
	cout<<ans<<endl;	
} 

P1091 合唱队形

分别正向、反向求最长上升子序列即可。

#include<iostream>
#include<algorithm>
using namespace std;
int n,t[110],f[110],g[110],ans; 
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>t[i]; 
	}
	//正向最长上升子序列 
	f[1]=1;	
	for(int i=2;i<=n;i++){
		for(int j=1;j<i;j++){
			if(t[i]>t[j]){
				f[i]=max(f[i],f[j]);
			}
		}
		f[i]+=1;
	}
	//逆向最长上升子序列
	g[n]=1; 
	for(int i=n-1;i>=1;i--){
		for(int j=n;j>i;j--){
			if(t[i]>t[j]){
				g[i]=max(g[i],g[j]);
			}
		}
		g[i]+=1;
	}
	for(int i=1;i<=n;i++){
		ans=max(ans,f[i]+g[i]-1);
	}
	cout<<n-ans<<endl; 
} 

P5858 「SWTR-03」Golden Sword

80分代码

  • d p [ i ] [ j ] dp[i][j] dp[i][j]表示放入 i i i原料后,锅里总共有 j j j个原料时所得到的最大耐久值
  • d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ k ] + a [ i ] ∗ j ) , k ∈ [ j − 1 , m i n ( w , k − 1 + s ) ] dp[i][j]=max(dp[i-1][k]+a[i]*j),k\in[j-1,min(w,k-1+s)] dp[i][j]=max(dp[i1][k]+a[i]j),k[j1,min(w,k1+s)]
  • 初始化为-inf, d p [ 0 ] [ 0 ] = 0 dp[0][0]=0 dp[0][0]=0
#include<iostream>
#include<algorithm> 
using namespace std;
int n,w,s;
long long dp[5510][5510],inf=1e18,ans=-inf,a[5510];
int main(){
	cin>>n>>w>>s;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=0;i<=n;i++){
		for(int j=0;j<=w;j++){			
			dp[i][j]=-inf;
		}
	}
	dp[0][0]=0; 
	for(int i=1;i<=n;i++){
		for(int j=1;j<=w;j++){
			for(int k=j-1;k<=min(w,j+s-1);k++){
				dp[i][j]=max(dp[i][j],dp[i-1][k]+a[i]*j); 
			}
		}
	}
	for(int j=1;j<=w;j++){
		ans=max(ans,dp[n][j]);
	}
	cout<<ans<<endl;
}

100分代码

  • 因为 a [ i ] ∗ j a[i]*j a[i]j是一个常数,将状态转移方程改为 d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ k ] ) + a [ i ] ∗ j , k ∈ [ j − 1 , m i n ( w , s + j − 1 ) ] dp[i][j]=max(dp[i-1][k])+a[i]*j,k\in[j-1,min(w,s+j-1)] dp[i][j]=max(dp[i1][k])+a[i]j,k[j1,min(w,s+j1)]
  • m a x ( d p [ j ] [ k ] ) max(dp[j][k]) max(dp[j][k])利用单调队列优化
  • 由于 k k k的取值范围是 [ j − 1 , m i n ( w , s + j − 1 ) ] [j-1,min(w,s+j-1)] [j1,min(w,s+j1)],同时存在 < j <j <j > j >j >j的部分,将状态方程改为 d p [ i ] [ j ] = m a x ( , d p [ i − 1 ] [ j − 1 ] , d p [ i − 1 ] [ k ] ) + a [ i ] ∗ j , k ∈ [ j , m i n ( w , s + j − 1 ) ] dp[i][j]=max(,dp[i-1][j-1],dp[i-1][k])+a[i]*j,k\in[j,min(w,s+j-1)] dp[i][j]=max(,dp[i1][j1],dp[i1][k])+a[i]j,k[j,min(w,s+j1)],这样只需要 j j j从大到小枚举维护单调队列即可
#include<iostream>
#include<algorithm>
#include<deque> 
using namespace std;
int n,w,s;
long long dp[5510][5510],inf=1e18,ans=-inf,a[5510];
int main(){
	cin>>n>>w>>s;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=0;i<=n;i++){
		for(int j=0;j<=w;j++){			
			dp[i][j]=-inf;
		}
	}
	dp[0][0]=0;	
	for(int i=1;i<=n;i++){
		//单调递减队列 
		deque<int>q;q.push_back(0); 
		for(int j=min(w,i);j>=1;j--){
			while(q.size()&&q.front()>j+s-1) q.pop_front();				
			while(q.size()&&dp[i-1][q.back()]<dp[i-1][j])q.pop_back();
			q.push_back(j);
			dp[i][j]=max(dp[i-1][q.front()],dp[i-1][j-1])+j*a[i];
		}
	}
	for(int j=1;j<=w;j++){
		ans=max(ans,dp[n][j]);
	}
	cout<<ans<<endl;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值