快乐dp

最玄学的dp。
放点水题吧
1.洛谷p1091 题意相当清晰,就是枚举中间最高人的位置
实现的时候发现了自已一直以来的理解失误。。

#include<iostream>
#include<cmath>
#include<stdio.h>
#include<algorithm>
using namespace std;
int n,a[101];
int dp1[101],dp2[101];
//先找最长上升,再找最长下降
int work(int x){
 for(int i=1;i<=n;i++){
  dp1[i]=1;dp2[i]=1;
 }
 //dp[i]表示的以a[i]为结尾。so dp[n]未必是1-n种最大的dp
 int m1=0;
 for(int i=1;i<=x;i++){
  for(int j=1;j<i;j++){
   if(a[j]<a[i])
   dp1[i]=max(dp1[i],dp1[j]+1);
  }
  m1=max(m1,dp1[i]);
 }
 int m2=0;
 for(int i=x+1;i<=n;i++){
  for(int j=x+1;j<i;j++){
   if(a[j]>a[i]){
    dp2[i]=max(dp2[i],dp2[j]+1);
   }
  }
  m2=max(m2,dp2[i]);
 }
 return m1+m2;
}
int main(){
 scanf("%d",&n);
 int ans=10000000;
 for(int i=1;i<=n;i++)scanf("%d",&a[i]);
 for(int i=1;i<=n;i++){
  ans=min(ans,n-work(i));
 }
 cout<<ans<<endl;
}

2.还是洛谷
p1280
题意:给k个区间,选取它们中不重叠的几个(如果能选不可不选),求最大覆盖长度(最小空域长度)
常规思想:问什么设什么dp,并且这里是倒序。感觉自己dp区间的选取都不太了解、

倒序方法 f[i]是指i-n时间内的休息时间

#include<iostream>
#include<cmath>
#include<string.h>
#include<stdio.h>
#include<iomanip>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
vector <int> a[100005];
int dp[10002]; 
bool book[10003];
int n,k;
int main(){
 cin>>n>>k;
 for(int i=1;i<=k;i++){
  int x,y;
  cin >> x >>y;
  a[x].push_back(y);
  book[x]=1;
  //book是记录工作点的,其实可以不用 
 }
 for(int i=n;i>=1;i--){
 //若不要工作,那么休息+1
  if(!book[i]) dp[i]=dp[i+1]+1;
  else{
  //如果要的话,那么选区工作区间外的那一块比较
  //终于看明白max()是选区考虑的意思了。。
   for(int j=0;j<a[i].size();j++){
    dp[i]=max(dp[i],dp[i+a[i][j]]);
   }
  }
 }
 cout<<dp[1]<<endl;
}

正序方法 f[i]是指1-i内的休息时间

#include<iostream>
#include<cmath>
#include<string.h>
#include<stdio.h>
#include<iomanip>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
vector <int> a[100005];
int dp[10002]; 
int n,k;
int main(){
 cin>>n>>k;
 for(int i=1;i<=k;i++){
  int x,y;
  cin >> x >>y;
  a[x].push_back(y);
 }
 for(int i=1;i<=n;i++)dp[i]=-4500;
 dp[1]=0;
 for(int i=1;i<=n;i++){
  if(a[i].size()==0){
   dp[i+1]=max(dp[i]+1,dp[i+1]); 
  }
  else{
   for(int j=0;j<a[i].size();j++){
   //这里有点玄学的说。
    int &x=dp[i+a[i][j]];
    x=max(x,dp[i]);
    //其实就是dp[i+a[i][j]]=max(dp[i+a[i][j]],dp[i]);
   }
  }
 }
 cout<<dp[n+1]<<endl; 
}

3石子合并 链状变环状
dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1])
i<k<j
o3

#include<iostream>
#include<cmath>
#include<string.h>
#include<stdio.h>
#include<iomanip>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
int n;
int dp1[300][300];
int dp2[300][300];
int a[300];
int s[300];
int main(){
 cin>>n;
 for(int i=1;i<=n;i++){
  cin>>a[i];
  a[i+n]=a[i];
 }
 for(int i=1;i<=2*n;i++){
  s[i]=s[i-1]+a[i];
 }
 for(int p=1;p<n;p++){
  for(int i=1,j=i+p;(i<2*n)&&(j<2*n);i++,j=i+p){
   dp2[i][j]=99999999;
   for(int k=i;k<j;k++){
    dp1[i][j]=max(dp1[i][j],dp1[i][k]+dp1[k+1][j]+s[j]-s[i-1]);
    dp2[i][j]=min(dp2[i][j],dp2[i][k]+dp2[k+1][j]+s[j]-s[i-1]);
   }
  }
 }
 int m1=0,m2=9999999;
 for(int i=1;i<=n;i++){
 //这是很多个链 dp1[i][i+n-1]就是在以ai为首,ai+n-1结尾的链中的最值
  m1=max(m1,dp1[i][i+n-1]);
  m2=min(m2,dp2[i][i+n-1]);
 }
 cout<<m2<<endl<<m1<<endl;
} 
/*
int dfs1(int l,int r){
 if(dp1[l][r]) return dp1[l][r];
 if(l==r) return dp1[l][r]=0;
 int res=0;
 for(int k=l;k<r;k++){
  res=max(res,dfs1(l,k)+dfs1(k+1,r)+s[r]-s[l-1]);
 }
 return dp1[l][r]=res;
}
int dfs2(int l,int r){
 if(dp2[l][r]) return dp2[l][r];
 if(l==r) return dp2[l][r]=0;
 int res=99999999;
 for(int k=l;k<r;k++){
  res=min(res,dfs2(l,k)+dfs2(k+1,r)+s[r]-s[l-1]); 
 }
 return dp2[l][r]=res; 
 }

4.纪念一下自己第一次做的dp吧 纯水题

#include<iostream>
#include<cmath>
#include<algorithm>
#include<string> 
using namespace std;
int a[1001][1001];
int dp[1001][1001];
int ans;
int n;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
 for(int j=1;j<=i;j++){
  cin>>a[i][j];
 }
}
dp[1][1]=a[1][1];
for(int i=2;i<=n;i++){
 for(int j=1;j<=i;j++){
  dp[i][j]=max(dp[i-1][j-1],dp[i-1][j])+a[i][j];
 }
}
for(int i=1;i<=n;i++)ans=max(ans,dp[n][i]);
cout<<ans<<endl;
}

5.多维dp模板 洛谷 方格取数&传纸条

#include<iostream>
#include<cmath>
#include<algorithm>
#include<string> 
using namespace std;
int dp[20][20][20][20];
int n;
int a[10][10]; 
int max(int x,int y,int z,in/t k){
 return max(max(x,y),max(k,z));
}
int main(){
 cin>>n;
 int x,y,z;
 while(cin>>x>>y>>z){
  if(x==0) break;
  a[x][y]=z;
 }
 for(int i=1;i<=n;i++){
  for(int j=1;j<=n;j++){
   for(int k=1;k<=n;k++){
    int l=i+j-k;
    if(l<=0)break;
    //dp[i][j][k][l]是第一个人走到i,j;第二人到k,j。
    //状态转移,讨论两个人上一步是从哪里走来
    dp[i][j][k][l]=max(dp[i-1][j][k-1][l],dp[i-1][j][k][l-1],
        dp[i][j-1][k][l-1],dp[i][j-1][k-1][l]);
    if(i==k&&j==l) dp[i][j][k][l]+=a[i][j];
    else dp[i][j][k][l]+=a[i][j]+a[k][l];
   }
  }
 }
 cout<<dp[n][n][n][n]<<endl;
}

三维优化*1 寻找另一个变量代替一维 这里用的是步数

#include<iostream>
#include<cmath>
#include<algorithm>
#include<string> 
using namespace std;
int dp[600][60][60];
int n,m;
int a[100][100]; 
int max(int x,int y,int z,int k){
 return max(max(x,y),max(z,k));
}
int main(){
 cin>>m>>n;
 int x,y,z;
 for(int i=1;i<=m;i++)
 for(int j=1;j<=n;j++)
 cin>>a[i][j]; 
 for(int i=1;i<=m+n-1;i++){
  for(int c=1;c<=m;c++){
   for(int b=1;b<=m;b++){
   dp[i][c][b]=max(dp[i-1][c-1][b-1],dp[i-1][c][b-1],dp[i-1][c-1][b],dp[i-1][c][b]);
   if(c!=b) 
   dp[i][c][b]+=a[c][i-c+1]+a[b][i-b+1];
   else dp[i][c][b]+=a[c][i-c+1]; 
   }
  }
 }
 cout<<dp[m+n-1][m][m]<<endl;
}

6.洛谷模板LCS

#include <cstdio>
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm> 
#include<iomanip>
#include<cmath>
#include<string>
using namespace std;
const int inf = 0x3f3f3f;
int n,len,a[100005],b[100005],f[100005];
int bound(int x){
 int l=1,r=len;
 while(l<r){
  int mid=(l+r)>>1;
  if(b[f[mid]]>b[x])
   r=mid;
   else l=mid+1;
 }
 return l;
}
int main(){
 cin>>n;
 //这是o2复杂度的简单解法 这道题里面不可用,数组会爆 ,tle
 /*
 for(int i=1;i<=n;i++)cin>>a[i];
 for(int i=1;i<=n;i++)cin>>b[i];
 for(int i=1;i<=n;i++){
  for(int j=1;j<=n;j++){
   dp[i][j]=max(dp[i][j-1],dp[i-1][j]);
   if(a[i]==b[j]){
    dp[i][j]=dp[i-1][j-1]+1;
   }
  }
 }
 cout<<dp[n][n]<<endl;
 */
 for(int i=1;i<=n;i++){
  cin >> a[i];
 }
 for(int i=1;i<=n;i++){
  int x;
  cin>>x;
  b[x]=i;
 }
 //数组b[i]里面存的是数字i在第二个数组中出现的位置
 for(int i=1;i<=n;i++) f[i]=inf;
 f[1]=0;
 len=0;
 for(int i=1;i<=n;i++){
 //如当前数字的出现位置 在 当前答案序列最末尾数字的出现位置 之后: 接上
  if(b[a[i]]>b[f[len]]){
   f[++len]=a[i];
  }
  else{
  //其实就是用二分找lower_bound的过程
  //如当前数字的出现 在 答案末尾之前 :更新(替换掉大于等于它的第一个数字)
   f[bound(a[i])]=a[i];
  }
 }
  cout << len<<endl;
  return 0; 
}

7.2019 icpc ShangHai_H
题意:好长啊 告辞(其实蛮好懂的 当时竟然gg了)

一个相当标致的dp
这个悲伤的故事告诉我们 当你超时的时候,就不要想着剪枝了。
还有 记得取模。
我去qaq一会儿

#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
int t,n,a[500];
const int mod=1e9+7;
//dp[x][y]第x个数起 到最后一个能组成y的方案数 
int dp[302][150005];
int sum,ans;
void ini(){
 for(int i=1;i<=sum;i++) dp[n+1][i]=0; 
 return;
}
int main(){
 cin >> t;
 while(t--){
  cin >> n;
  sum=0; ans=0;
  for(int i=1;i<=n;i++){
   cin >> a[i];
   sum+=a[i];
  }
  sort(a+1,a+1+n);
  ini();
  dp[n+1][0]=1;
  for(int i=n;i>=1;i--){
   for(int j=0;j<=sum;j++){
    dp[i][j]=dp[i+1][j];
    if(j>=a[i]){
     dp[i][j]+=dp[i+1][j-a[i]];
     if(j*2>=sum && j-a[i]<= sum-j) ans+=dp[i+1][j-a[i]];
     ans%=mod; 
    }
    dp[i][j]%=mod;
   } 
  }
  cout << ans << endl;
 }
 return 0;
}

7.好 新鲜出炉的树形dp(x 记忆化搜索
洛谷P1040加分二叉树

#include<iostream>
using namespace std;
int n;
//pre记录前序遍历 s记录所选的序号 p是每层最优解 
int pre[50],s[35][35],p[35][35];
//记忆化搜索 
int dfs(int l,int r){
 if(l>r) return 1;
 if(l==r){
  s[l][r]=l;
  return p[l][r]=pre[l];
 }
 int ans=0;
 long long num=1;
 //记忆化 
 if(p[l][r]) return p[l][r];
 for(int i=l;i<=r;i++){
  //枚举每一层的根节点,记录答案 
  long long t=dfs(l,i-1)*dfs(i+1,r)+pre[i];
  if(ans<t){
   ans=t;
   num=i;
  }
 }
 s[l][r]=num;
 return p[l][r]=ans;
}
void search(int l,int r){
 if(l>r) return;
 cout<< s[l][r]<<" ";
 search(l,s[l][r]-1);
 search(s[l][r]+1,r);
 return;
}
int main(){
 cin >> n;
 for(int i=1;i<=n;i++)cin>>pre[i];
 cout<<dfs(1,n)<<endl;
 search(1,n);
 return 0;
}

8 洛谷P2258子矩阵
我觉得这是个好题 嗯

#include <iostream>
#include<stdio.h>
#include<stdlib.h> 
#include <cmath>
using namespace std;
//按行来DP(最后还是要dp呢)
int n,m,r,c;
int a[20][20];
int cnt=1;
//cc记录单独每列中上下元素的差的绝对值之和
//rc[i][j]记录第i行和第j行之间相邻元素的差的绝对值之和 
int book[20],cc[20],rc[20][20];
int f[20][20];
void ini(){
 //记得只能算选用过的嗷 
 for(int i=1;i<=m;i++){
  cc[i]=0; 
  for(int j=1;j<r;j++)
   cc[i]+=abs(a[book[j]][i]-a[book[j+1]][i]);
 } 
 for(int i=2;i<=m;i++){
  for(int j=1;j<i;j++){
   rc[i][j]=0;
   for(int k=1;k<=r;k++){
    rc[i][j]+=abs(a[book[k]][i]-a[book[k]][j]);
   } 
  }
 }
}
int cmin;
int ans=0x3f3f3f3f;
void dp(){
 for(int i=1;i<=m;i++){
  cmin=min(c,i);
  for(int j=1;j<=cmin;j++){
   if(j==1){
    f[i][j]=cc[i];
   } 
   else if(i==j){
    f[i][j]=cc[i]+f[i-1][j-1]+rc[i][j-1];
   }
   else{
    f[i][j]=0x3f3f3f;
   
    for(int k=j-1;k<i;k++)
     f[i][j]=min(f[i][j],f[k][j-1]+cc[i]+rc[i][k]);
   }
   if(j==c){
    ans=min(ans,f[i][j]);
   }
  }
 }
} 
void dfs(int x){
 if(x>n){
  ini();
  dp();
  return; 
 }
 //剪枝 
 if(r-cnt==n-x){
  book[cnt++]=x;
  dfs(x+1);
  book[cnt--]=0;
  return; 
 } 
 //不取当前的这一行 
 dfs(x+1);
 //取这一行 
  book[cnt++]=x;
  dfs(x+1);
  book[cnt--]=0;
 return ;
} 
int main(){
 cin>>n>>m>>r>>c;
 for(int i=1;i<=n;i++)
  for(int j=1;j<=m;j++)
   cin>>a[i][j];
  dfs(1);
  cout<<ans<<endl;
  return 0;
} 

仔细一看发现都忘了,太惨了。
简单DP(喜欢用DFS代替orz
P1802 简单的01背包
注意状态转移内外层

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<string.h>
#include<algorithm>
#include<string>
using namespace std;
const int maxn = 1e4;
int dp[maxn], win[maxn], lose[maxn],w[maxn];
int n, x;
int main() {
 cin >> n >> x;
 for (int i = 1;i <= n;i++) {
  cin >> lose[i] >> win[i] >> w[i];
 }
 for (int i = 1;i <= n;i++) {
  for (int j = x;j >= 0;j--) {
   if (j >= w[i]) {
    dp[j] = max(dp[j] + lose[i], dp[j - w[i]] + win[i]);
   }
   else {
    dp[j] = dp[j] + lose[i];
   }
  }
 }
 cout << dp[x]*5 << endl;
 return 0;
}

P2196 挖地雷 第一眼以为是图论题orz
转移方程 dp[i]代表以i为结尾的最大地雷数
dp[i] =max(dp[i],dp[j]) ( j与i相连 & j在i前面)
顺便记录j为i的前驱

向dfs屈服


#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#include <cmath>
#include <vector>
#include <queue>
#include <string>
#include <bitset>
//#include <unordered_map>
//#include <unordered_set>
#define INF 0x3f3f3f3f
#define sz sizeof 
using namespace std;
const int maxn = 250;
int n,a[maxn],mp[maxn][maxn],vis[maxn];
int ans[maxn], res = 0,tmp[maxn],len=0;
void dfs(int x,int cur,int dep) {
 if (cur > res) {
  res = cur; len = dep;
  for (int i = 1;i <= dep;i++) {
   ans[i] = tmp[i];
   //cout << ans[i] << " ~~ ";
  }
  //cout << endl;
 }
 if (x == n) return;
 for (int i = x+1;i <= n;i++) {
  if (mp[x][i] && !vis[i]) {
   vis[i]=1;
   dep++;tmp[dep] = i;
   dfs(i,cur+a[i],dep);
   vis[i]=0;
   tmp[dep] = 0;dep--;
  }
 }
 return;
}
void ini() {
 for (int i = 1;i <= n;i++) {
  vis[i] = 0;tmp[i] = 0;
 }
}
int main() {
 cin >> n;
 for (int i = 1;i <= n;i++) {
  cin >> a[i];
  ans[i] = a[i];
 }
 for (int i = 1;i < n;i++) {
  for (int j = i + 1;j <= n;j++) {
   int x;cin >> x;
   mp[i][j] = mp[j][i] = x;
  }
 }
 for (int i = 1;i <= n;i++) {
  ini();
  tmp[1] = i;vis[i] = 1;
  dfs(i, a[i], 1);
 }
 for (int i = 1;i <= len;i++)
  cout << ans[i] << " ";
 cout << endl<<res << endl;
 return 0;
}

DP正解

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#include <cmath>
#include <vector>
#include <queue>
#include <string>
#include <bitset>
//#include <unordered_map>
//#include <unordered_set>
#define INF 0x3f3f3f3f
#define sz sizeof 
using namespace std;
const int maxn = 250;
int n, a[maxn], mp[maxn][maxn], dp[maxn];
int pre[maxn],res;
int book;
void print(int x) {
 if (!pre[x]) { cout << x << " "; return;}
 print(pre[x]);
 cout << x << " ";
 return;
}
int main() {
 cin >> n;
 for (int i = 1;i <= n;i++) {
  cin >> a[i];
 }
 for (int i = 1;i < n;i++) {
  for (int j = i + 1;j <= n;j++) {
   int x;cin >> x;
   mp[i][j] = mp[j][i] = x;
  }
 }
 for (int i = 1;i <= n;i++) {
  for (int j = 1;j <= n;j++) {
   if (mp[i][j] && dp[j]>dp[i]) {
    dp[i] = dp[j];
    pre[i] = j;
   }
  }
  dp[i] += a[i];
  if (dp[i] > res) {
   res = dp[i];
   book = i;
  }
 }
 print(book);
 cout << endl;
 cout << res << endl;
 return 0;
}

P4017 最大食物链
向记忆化dfs屈服
((菜如我我需要一个拓扑排序模板


#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#include <cmath>
#include <vector>
#include <queue>
#include <string>
#include <bitset>
//#include <unordered_map>
//#include <unordered_set>
#define INF 0x3f3f3f3f
#define sz sizeof 
using namespace std;
const int maxn = 5005;
int n, m;
vector<int> v[maxn];
bool impossible[maxn];
long long dp[maxn], res = 0;
const int mod = 80112002;
long long dfs(int x) {
 long long tmp = 0;
 if (!v[x].size()) return dp[x] = 1;
 for (int j = 0;j<v[x].size();j++) {
  int i= v[x][j];
  tmp += dp[i] ? dp[i] : dfs(i);
  tmp %= mod;
 }
 return dp[x] = tmp;
}
int main() {
 cin >> n >> m;
 for (int i = 1;i <= m;i++) {
  int a, b;cin >> a >> b;
  //b吃a
  v[b].push_back(a);
  impossible[a] = true;
 }
 for (int i = 1;i <= n;i++) {
  if (!impossible[i]) {
   res = (res + dfs(i)) % mod;
  }
 }
 cout << res << endl;
 return 0;
}

线性DP
P2758 编辑距离,求字符串A经过增减改三种操作最少多少步能够和B串一样。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<string.h>
#include<algorithm>
#include<string>
using namespace std;
const int maxn = 1e4,inf = 0x3f3f3f3f;
string A, B;
int la, lb,dp[maxn][maxn];
int min(int a, int b, int c) {
 return min(a, min(b, c));
}
int main() {
 cin >> A >> B;
 la = A.length(), lb = B.length();
 for (int i = 0;i <= la;i++) {
  dp[i][0] = i;
 }
 for (int i = 0;i <= lb;i++) {
  dp[0][i] = i;
 }
 for (int i = 1;i <= la;i++) {
  for (int j = 1;j <= lb;j++) {
   int add = 1;
   if (A[i - 1] == B[j - 1]) add--;
   dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + add);
  }
 }
 cout << dp[la][lb] << endl;
 return 0;
}

环形DP和区间dp
今天补了洛谷的区间dp模板题
深刻意识到自己的菜啦
[NOI1995]石子合并 经典

P1063 能量项链 环状的区间dp 和石子合并类似
注意控制枚举时候的区间长度(2-n+1)注意要加一,因为n+1就是1和n接头的情况。

P3146 [USACO16OPEN]248 G
差不多

P1220 关路灯
是比较复杂的dp
三维(看答案的 不会做)
dp[i][j][k] 熄灭区间(i-j)范围内的灯后,人在k位置(左边 0 or 右边1)时的最小电能消耗量。
状态转移方程
dp[i][j][0]=min(dp[i+1][j][0]+(LEN)*(sum(1-i) + sum(j+1 - n)), dp[i+1][j][1] + LEN * (sum(1-i)+sum(j+1 - n)))

dp[i][j][1] =min(dp[i][j-1][0]+LEN (sum(1-(i-1))+sum(j-n)), dp[i][j-1][1]+LEN(sum(1-(i-1)) + sum(j-n)))
痛苦的公式,还要初始化 还是代码注释吧。
初始化:
往前一步
dp[c][c+1][1]=1 * (sum(1-c-1)+sum(c-n))
退后一步
dp[c][c-1][0]=1*(sum(1-c-1)+sum(c+1-n))
一步一步

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#include <cmath>
#include <vector>
#include <queue>
#include <string>
#include <bitset>
//#include <unordered_map>
//#include <unordered_set>
#define INF 0x3f3f3f3f
#define sz sizeof 
using namespace std;
const int maxn = 250;
int n, c,pos[maxn];
int dp[maxn][maxn][3];
int sum[maxn][maxn],s[maxn];
int power[maxn];
int dis(int i, int j) {
 return pos[i] - pos[j];
}
int main() {
 cin >> n >> c;
 for (int i = 1;i <= n;i++) {
  cin >> pos[i] >> power[i];
  s[i] = s[i - 1] + power[i];
 }
 for (int i = 1;i <= n;i++) {
  for (int j = i;j <= n;j++) {
   sum[i][j] += (s[j]-s[i-1]);
  }
 }
 memset(dp, INF, sizeof(dp));
 if(c+1<=n)
 dp[c][c + 1][1] = dis(c+1,c)*(sum[1][c-1] + sum[c+1][n]);
 //竟然打出了dp[c][c-1]这种东西...........
 if(c-1>=1)
 dp[c-1][c][0] = dis(c,c-1)*(sum[1][c - 1] + sum[c + 1][n]);
 
 for (int len = 3;len <= n;len++) {
  for (int i = 1;i <= n-len+1;i++) {
   int j = i + len - 1;
   dp[i][j][0] = min(dp[i+1][j][0] + dis(i+1,i)*(sum[1][i] + sum[j+1][n]),
                  dp[i+1][j][1] + dis(j,i)*(sum[1][i] + sum[j+1][n]));
   dp[i][j][1] = min(dp[i][j-1][0] + dis(j,i)*(sum[1][i-1] + sum[j][n]),
                  dp[i][j-1][1] + dis(j,j-1)*(sum[1][i-1] + sum[j][n]));
  }
 }
 int ans = min(dp[1][n][0], dp[1][n][1]);
 cout << ans << endl;
 return 0;
}

P3205 [HNOI2010]合唱队
我觉得列状态是最重要的了
dp[i][j][0]:第i个人从左边进来
dp[i][j][1]:第j个人从右边进来
状态转移:
dp[i][j][0] += dp[i+1][j][0] (a[i]<a[i+1])
dp[i][j][0] += dp[i+1][j][1] (a[i]<a[j])
dp[i][j][0] += dp[i][j-1][0] (a[j]>a[i])
dp[i][j][1] += dp[i][j-1][0] (a[j]>a[j-1])
初始化 dp[i][i][0]=1

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#include <cmath>
#include <vector>
#include <queue>
#include <string>
#include <bitset>
#include<iomanip>
#define INF 0x3f3f3f3f
#define ll long long 
using namespace std;
const int maxn = 1e3 + 5, mod = 19650827;
int n, a[maxn];
ll dp[maxn][maxn][3];
int main(){
 cin >> n;
 for (int i = 1;i <= n;i++) {
  cin >> a[i];
  dp[i][i][0] = 1;
 }
 for (int len = 1;len < n;len++) {
  for (int i = 1; i <= n;i++) {
   int j = i + len; if (j > n) break;
   if (a[i] < a[i + 1])
    dp[i][j][0] += dp[i + 1][j][0];
   if (a[i] < a[j]) 
    dp[i][j][0] += dp[i + 1][j][1];
   if (a[j] > a[j - 1]) 
    dp[i][j][1] += dp[i][j - 1][1];
   if (a[j] > a[i])
    dp[i][j][1] += dp[i][j - 1][0];
   dp[i][j][0] %= mod;
   dp[i][j][1] %= mod;
  }
 }
 cout << (dp[1][n][1] + dp[1][n][0])%mod << endl;
 return 0;
}

在这里插入图片描述
题意:给数组分块,每一块需要k的代价,另外同一块中的相同颜色也需要付出[相同个数]的代价。
dp转移方称:
维护cost[i][j]代表合并i-j需要符出的代价。
对每个数字f[i],开一个map记录后续出现的相同数字,
(1) mp[f[i]]<2 , 不用付出额外的代价,cost[i][j]=cost[i][j-1]
(2) mp[f[i]]=2 , 付出代价2,cost[i][j]=cost[i][j-1]+2
(3) mp[f[i]]>2 之前已经付出过代价2,现在再多了一个重复的,代价+1,cost[i][j]=cost[i][j-1] +1

然后用dp,dp[i]=min(dp[j]+cost[j+1][i]+k,dp[i])
每个dp初始化为1-i的代价。
意思是考虑前i个的dp可以在中间插入j(断开),把j+1-i的部分独立作一个堆,另外付出k的代价。
最后ans=dpn

#include<iostream>
#include<iomanip>
#include<cmath>
#include<string.h>
#include<string>
#include<iomanip>
#include<map>
#include<vector>
#include<algorithm>
#define inf 0x3f3f3f3f
using namespace std;
const int maxn = 2e3 + 5;
const int mod = 1e9 + 7;

int c[maxn][maxn];
int dp[maxn],f[maxn];
int main() {
	int t;
	cin >> t;
	while (t--) {
		int n, k;
		cin >> n >> k;
		memset(c, 0, sizeof(c));
		memset(dp, 0, sizeof(dp));
		memset(f,0, sizeof(f));

		for (int i = 0;i < n;i++) {
			cin >> f[i];
		}
		for (int i = 0;i < n;i++) {
			map<int, int> mp;
			mp[f[i]]++;
			for (int j = i + 1;j < n;j++) {
				mp[f[j]]++;
				if (mp[f[j]] == 2) c[i][j]= c[i][j-1] + 2;
				else if (mp[f[j]] > 2) 
					c[i][j] = c[i][j - 1] + 1;
				else c[i][j] = c[i][j - 1];
			}
		}
		for (int i = 0;i < n;i++) {
			dp[i] = c[0][i] + k;
			for (int j = 0;j < i;j++) {
				dp[i] = min(dp[i], dp[j] + c[j+1][i] + k);
			}
		}
		cout << dp[n-1] << endl;
	}
	return 0;
}

单调队列优化DP
洛谷P5858 Golden Sword
注意这里的inf要开的足够小
const long long llinf = 0x3f3f3f3f3f3f3f3f;
并且使用了单调队列优化
单调队列题目:洛谷P1440板子题,要用scanf


#include<iostream>
#include<queue>
#include<algorithm>
#include<map>
#include<cmath>
#include<cstdio>
#include<string>
#include<string.h>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 5e6 + 5;
#define eps 1e-8
int n,m, a[maxn], q[maxn];
int main() {
	scanf("%d %d",&n,&m);
	for (int i = 1;i <= n;i++) {
    	scanf("%d",&a[i]);
	}
	int head = 1, tail = 0;
	for (int i = 1;i <= n;i++) {
		printf("%d\n",a[q[head]]);
		//当前不满足要求,head移动
		//注意,这里为什么是i-q[head] +1>m呢,因为一开始head=1,队中的元素已经有了一个0.相当于多了一个元素,因此往前看m个的条件就要在这基础上+1
		while (i - q[head]> m -1 && tail >= head) head++;
		//当前有更优的,弹出,维护单调队列
		while (a[q[tail]] > a[i] && tail >=head) tail--;
		q[++tail] = i;
	}
	return 0;
}
#头文件,懒
const long long llinf = 0x3f3f3f3f3f3f3f3f;
int n, w, s;
int a[maxn];
long long dp[5505][5505],q[maxn],pos[maxn];
int main() {
	long long ans = -llinf;
	// dp[i][j]第i个放入时,有j种原料
	// k = j-1 -- min(w, j+s-1)
	//枚举k,dp[i][j]=max(dp[i][j],dp[i][j-1][k]+a[i]*j])
	cin >> n >> w >> s;
	for (int i = 0;i <= n  ;i++) {
		for (int j = 0;j <= w;j++) {
			dp[i][j] = -llinf;
		}
	}
	dp[0][0] = 0;
	for (int i = 1;i <= n;i++)cin >> a[i];
	for (int i = 1;i <= n;i++) {
		int l = 1, r = 1;
		q[l] = dp[i - 1][w];
		pos[l] = w;
		for (long long j = w;j;j--) {
			//若当前的可移除的个数大于s,出队
			while (pos[l] > j + s - 1 && l <= r) l++;
			//若不是最优解(当前比队尾还大,移除队尾
			while (q[r] < dp[i - 1][j - 1] && l <= r) r--;
			//入队
			pos[++r] = j - 1;
			q[r] = dp[i - 1][j - 1];
			dp[i][j] = q[l] + j * a[i];
		}
	}/*
	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;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值