最玄学的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;
}