近期比赛的 dp 总结

文章讲述了如何利用优先队列和区间动态规划解决两个具体问题:建桥墩问题中最小代价的计算,以及区间操作的背包问题。通过维护区间内的最小值,结合优先队列的特性优化计算效率。
摘要由CSDN通过智能技术生成

1. 优先队列维护区间dp 

933 Div 3 E. Rudolf and k Bridges

        令 dp[i] 表示在 i 处建桥墩,所需的最小代价和

        dp[i]=max(dp[i],dp[j]+c[i]+1)   \left ( i-d-1\leq j\leq i-1 \right )

        显然,枚举每个点前面d个区间会超时,考虑优先队列维护区间最小值,在优先队列存入每个桥墩的值和位置,每次算dp的时候,如果堆顶位置小于 i-d-1 则弹出。

#include <iostream>
#include <queue>
#include <algorithm>
#include <cstring>
#include<utility> 
#define int long long
using namespace std;
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > Q;
int dp[210000];
int store[110];
signed main(){
		int t;cin>>t;
		while(t--){
			int n,m,k,d,minn=1;cin>>n>>m>>k>>d;
			for(int i=0;i<n;i++){
					for(int j=1;j<=m;j++){
						dp[j]=0;
					}
					for(int j=1;j<=m;j++){
						int x;cin>>x;
						pair<int,int> tmp;
						if(Q.size()){
							while(Q.top().second<j-d-1){
								Q.pop();
							}
							minn=Q.top().second;	
						}
						dp[j]=dp[minn]+x+1;
//							cout<<i<<" "<<minn<<" "<<x<<endl;
						tmp.first=dp[j],tmp.second=j;
						Q.push(tmp);
					}
				while(Q.size()){
					Q.pop();
				}
				store[i]=dp[m];
			}
			int ans=0;
			int l=0,r=k-1;
			for(int i=0;i<k;i++){
				ans+=store[i];
			}
			int sum1=ans;
			for(int i=0;r+1+i<n;i++){
				sum1-=store[l+i];
				sum1+=store[r+1+i];
				if(sum1<ans) ans=sum1;
			}
			cout<<ans<<endl;
		}
		
		return 0;
}

2. 区间dp:

Edu 165 C. Minimizing the Sum

拿 dp[i][j] 表示dp[前 i 个数字][前 j 次操作] 的最大值。

不单单看一个点到一个点的转移,我们看区间的转移,dp[i][j] 的值, 显然是与区间 i−k 到 i 这段有关的,因为一个数字它能改变的最大区域就是它相邻的 k 个数字,那么1到i−k−1 之前的这一段区域如何操作都不会影响 dp[i][j] 的值,所以它是无后效性的。

状态转移方程

dp[i][j]=min(dp[i-u-1][j-u]+mn*(u+1))(1\leq u\leq k)

其中mn是区间 i-u 到 i 的最小值。

#include <iostream>
#include <cstring>

using namespace std;
#define int long long

int a[310000],dp[310000][11];

signed main(){
	int t;cin>>t;
	while(t--){
		int n,k;cin>>n>>k;
		for(int i=1;i<=n;i++){
			cin>>a[i];
		}
		for(int i=1;i<=n;i++){
			for(int j=1;j<=k;j++){
				dp[i][j]=1e18;
			}
		}//最小值,所以要设置max
		
		for(int i=1;i<=n;i++){
			for(int j=0;j<=k;j++){
				int mn=a[i];
				dp[i][j]=dp[i-1][j]+a[i]; 
              //如果之前已经操作过k次了,dp也需要继承,如果j为0,dp需要初始化,显然是前缀和的样子。
				for(int u=1;u<=j&&i-u-1>=0;u++){		
					mn=min(mn,a[i-u]);
					dp[i][j]=min(dp[i-u-1][j-u]+(u+1)*mn,dp[i][j]);
				}	
			}
		}	
		cout<<dp[n][k]<<endl;
	}
	return 0;
}

3. 组合背包类型的dp 

组合字符串:
ABC 344 D - String Bags (atcoder.jp) 

Let dp[ how many bags were processed? ][ how long is the prefix of T that matched? ]= { minimum money required }.

借题解的说明:

dp[i][j]表示dp[选了前 i 个包][能拼成 j 的长度]=所需的最小值。

如果能接上的话(即对每个起点都遍历):

dp[i+1][j+len]=min(dp[i][j]+1,dp[i+1][j+len])

#include <iostream>
#include <string>
#include <cstring>
#define inf 0x3f3f3f3f
using namespace std;
int dp[1100][1100];
int main(){
	string s;cin>>s;
	string s1;
	int n;cin>>n;
	for(int i=0;i<1100;i++){
		for(int j=0;j<1100;j++){
			dp[i][j]=1e9;
		}
	}
	dp[0][0]=0;
	for(int i=0;i<n;i++){
		int x;cin>>x;
		for(int j=0;j<=1100;j++){
			dp[i+1][j]=dp[i][j];
		}
		for(int j=1;j<=x;j++){
			cin>>s1;
			int sl=s1.length();
			//找到符合的位置的前缀
			for(int u=0;u+sl<=s.length();u++){
				bool judge=true;
				for(int v=0;v<sl;v++){
					if(s1[v]!=s[u+v]&&v<s.length()){
						judge=false;break;
					}
				}
				if(judge)
				dp[i+1][sl+u]=min(dp[i][u]+1,dp[i+1][sl+u]);
			} 
		}
	}
	if(dp[n][s.length()]>=5e8) cout<<"-1"<<endl;
	else cout<<dp[n][s.length()]<<endl;
//			for(int i=1;i<=12;g++){
//				for(int j=1;j<=12;j++){
//					cout<<dp[i][j]<<" ";
//				}
//				cout<<endl;
//			}
	return 0;
}

组合数字,求一堆数字和能不能达到k

D - Flip and Adjust

 不考虑输出单纯考虑能不能达到k,

dp[i][j]表示dp[前 i 张卡片][能组合的数字 j ]

有转移方程:

dp[i][j+a[i][0]]=dp[i-1][j]+a[i][0]

dp[i][j+a[i][1]]]=dp[i-1][j]+a[i][1]

最后查询dp[n][m]即可。

挖个状压dp和背包的坑,有空回来补

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值