1. 优先队列维护区间dp
933 Div 3 E. Rudolf and k Bridges
令 dp[i] 表示在 i 处建桥墩,所需的最小代价和
显然,枚举每个点前面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:
拿 dp[i][j] 表示dp[前 i 个数字][前 j 次操作] 的最大值。
不单单看一个点到一个点的转移,我们看区间的转移,dp[i][j] 的值, 显然是与区间 i−k 到 i 这段有关的,因为一个数字它能改变的最大区域就是它相邻的 k 个数字,那么1到i−k−1 之前的这一段区域如何操作都不会影响 dp[i][j] 的值,所以它是无后效性的。
状态转移方程
其中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 的长度]=所需的最小值。
如果能接上的话(即对每个起点都遍历):
#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
不考虑输出单纯考虑能不能达到k,
dp[i][j]表示dp[前 i 张卡片][能组合的数字 j ]
有转移方程:
最后查询dp[n][m]即可。
挖个状压dp和背包的坑,有空回来补