赞
踩
一般有两种实现方式
https://www.acwing.com/problem/content/284/
#include<iostream> #include<algorithm> using namespace std; const int N=310; int n; int s[N]; int f[N][N]; int main(){ scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%d",&s[i]); for(int i=1;i<=n;i++)s[i]+=s[i-1]; for(int lenn=2;lenn<=n;lenn++) for(int i=1;i+lenn-1<=n;i++){ int l=i,r=i+lenn-1; f[l][r]=1e9; for(int k=l;k<r;k++) f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]); } printf("%d",f[1][n]); return 0; }
https://www.acwing.com/problem/content/1070/
具体思路:
如何转化成上一个问题来做?
上一题是给定一个区间,我们都可以求出区间内石子合并的最小代价
如何把环转化为区间?
环上每两个点连一条边,表示其可以合并,我们最多合并n-1次,所以n个点的环上最多会连n-1条边,
也就是最终会有一个缺口
将这个缺口展开,可以得到一条链
只需对这条链进行上题的石子合并即可
以缺口为分界点,枚举每个缺口,可以得到n条链
问题转化为对n条链分别做石子合并
上题的时间复杂度是n^3,
枚举缺口有n^4的复杂度,n=200时有8亿,会超时
这里有一种普遍的优化方式
我们本质上要求n条长度为n的链上的石子合并问题
我们把链复制一遍接到后边即可
再在所有长度是n的区间 f [ i, i+n-1 ] 中取最值即可
(2n)^3 ,n=200时有64000000,不会超时
#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N=410, inf=0x3f3f3f3f; int n; int s[N],w[N]; int f[N][N], g[N][N]; int main(){ cin>>n; for(int i=1;i<=n;i++){ cin>>w[i]; w[i+n]=w[i]; } for(int i=1;i<=n*2;i++)s[i]=s[i-1]+w[i];//预处理出前缀和 memset(f, 0x3f, sizeof f); memset(g, -0x3f, sizeof g); for(int len=1;len<=n;len++) for(int l=1; l+len-1<=n*2;l++){ int r=l+len-1; if(len==1)f[l][r]=g[l][r]=0;//一堆就不用合并了 else{ for(int k=l;k<r;k++){ f[l][r]=min(f[l][r], f[l][k]+f[k+1][r]+s[r]-s[l-1]); g[l][r]=max(g[l][r], g[l][k]+g[k+1][r]+s[r]-s[l-1]); } } } int minv=inf, maxv=-inf; for(int i=1;i<=n;i++){ minv=min(minv, f[i][i+n-1]); maxv=max(maxv, g[i][i+n-1]); } cout<<minv<<endl<<maxv<<endl; return 0; }
https://www.acwing.com/problem/content/322/
有点像矩阵乘法
链式的区间dp
集合还是以分界线的形式划分
取巧的表示珠子的方法
f[1,5] 就是答案
线性做法:
环形做法:
这题是把一个珠子断开,首位有相同的珠子
有n个珠子,有n种断开方式
和上一题差不多
#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N=210; int n; int w[N]; int f[N][N]; int main(){ cin>>n; for(int i=1;i<=n;i++){ cin>>w[i]; w[i+n]=w[i]; } for(int len=3; len<=n+1; len++)//长度是2表示只有一个矩阵,是不需要释放能量的 for(int l=1;l+len-1<=n*2;l++){ int r=l+len-1; for(int k=l+1;k<r;k++) f[l][r]=max(f[l][r], f[l][k]+f[k][r]+w[l]*w[k]*w[r]); } int res=0; for(int i=1;i<=n;i++)res=max(res, f[i][i+n]); cout<<res<<endl; return 0; }
https://www.acwing.com/problem/content/1071/
这题和上一题的状态转移方程完全一样
可以发现三角形的左右两边是独立的,也就是这个三角形将多边形划分成了3个部分,每个部分取最值
这题要写个高精度
一般先写不是高精度的过掉样例,再写成高精度的
无高精度
#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N=55, M=35;//9*3+2+保险 const int inf=1e9; int n; int w[N]; int f[N][N]; int main(){ //虽然是环,但实际上是一个链式dp cin>>n; for(int i=1;i<=n;i++)cin>>w[i]; for(int len=3;len<=n;len++) for(int l=1;l+len-1<=n;l++){ int r=l+len-1; f[l][r]=inf; for(int k=l+1;k<r;k++) f[l][r]=min(f[l][r], f[l][k]+f[k][r]+w[l]*w[k]*w[r]); } cout<<f[1][n]<<endl; return 0; }
偷懒数组模拟高精度
#include<iostream> #include<algorithm> #include<cstring> using namespace std; typedef long long ll; const int N=55, M=35;//9*3+2+保险 const int inf=1e9; int n; ll w[N]; ll f[N][N][M]; void add(ll a[], ll b[]){ static ll c[M]; memset(c, 0, sizeof c); for(int i=0, t=0; i<M; i++){ t+=a[i]+b[i]; c[i]=t%10; t/=10; } memcpy(a, c, sizeof c); } void mul(ll a[], ll b){ static ll c[M]; memset(c, 0, sizeof c); ll t=0; for(int i=0;i<M;i++){ t+=a[i]*b; c[i]=t%10; t/=10; } memcpy(a, c, sizeof c); } int cmp(ll a[], ll b[]){ for(int i=M-1; i>=0; i--)//从最高位往前比 if(a[i]>b[i])return 1; else if (a[i]<b[i])return -1; return 0; } void print(ll a[]){ int k=M-1; while(k&&!a[k])k--;//去0 while(k>=0)cout<<a[k--]; cout<<endl; } int main(){ //虽然是环,但实际上是一个链式dp cin>>n; for(int i=1;i<=n;i++)cin>>w[i]; ll temp[M]; for(int len=3;len<=n;len++) for(int l=1;l+len-1<=n;l++){ int r=l+len-1; f[l][r][M-1]=1;//正无穷 for(int k=l+1;k<r;k++){ memset(temp, 0, sizeof temp); temp[0]=w[l]; mul(temp, w[r]); mul(temp, w[k]); add(temp, f[l][k]); add(temp, f[k][r]); if(cmp(f[l][r], temp)>0) memcpy(f[l][r], temp, sizeof temp); } } print(f[1][n]); return 0; }
区间dp问题记录方案
https://www.acwing.com/problem/content/481/
前序遍历
中序遍历
后序遍历
#include<iostream> #include<algorithm> using namespace std; #include<cstring> const int N=30; int n; int w[N]; int f[N][N], g[N][N];//g存某段区间上的跟节点 void dfs(int l, int r){//前序遍历 if(l>r)return; int root=g[l][r]; cout<<root<<' '; dfs(l, root-1); dfs(root+1, r); } int main(){ cin>>n; for(int i=1;i<=n;i++)cin>>w[i]; for(int len=1;len<=n;len++) for(int l=1;l+len-1<=n;l++){ int r=l+len-1; if(len == 1) { f[l][r]=w[l];//如果是叶节点,就等于本身分数 g[l][r]=l;//跟节点就是他自己 } else { for(int k=l ; k<=r ; k++){ int left = k==l ? 1:f[l][k-1];//如果左子树为空,就是1 int right = k==r ? 1:f[k+1][r];//如果右子树为空,就是1 int score = left*right +w[k]; if(f[l][r]<score){ f[l][r]=score; g[l][r]=k; } } } } cout<<f[1][n]<<endl; dfs(1, n); return 0; }
https://www.acwing.com/problem/content/323/
#include<iostream> #include<algorithm> using namespace std; //二维前缀和 + 记忆化搜索(状态表示太长了,有5维) #include<cstring> #include<cmath> const int N=15, M=9; const double inf=1e9; int n, m=8; int s[M][M]; double f[M][M][M][M][N]; double X;//平均数 double get(int x1, int y1, int x2, int y2){ double sum=s[x2][y2]-s[x2][y1-1]-s[x1-1][y2]+s[x1-1][y1-1]-X; return sum*sum/n; } double dp(int x1, int y1, int x2, int y2, int k){ double &v = f[x1][y1][x2][y2][k]; if(v>=0) return v; if(k==1) return v=get(x1, y1, x2, y2);//不能再切撩 v=inf; for(int i=x1; i<x2; i++){//横着切 v=min(v, dp(x1, y1, i, y2, k-1)+get(i+1, y1, x2, y2));//选上部分继续切 v=min(v, dp(i+1, y1, x2, y2, k-1)+get(x1, y1, i, y2));//选下部分继续切 } for(int i=y1; i<y2;i++){//竖着切 v=min(v, dp(x1, y1, x2, i, k-1)+get(x1, i+1, x2, y2));//选左边 v=min(v, dp(x1, i+1, x2, y2, k-1)+get(x1, y1, x2, i));//选右边继续切 } return v; } int main(){ cin>>n; for(int i=1;i<=m;i++) for(int j=1;j<=m;j++){ cin>>s[i][j]; s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1]; } memset(f, -1, sizeof f); //double数组存储是科学计数法,如果二进制全是1的表示是负无穷-NAN X=(double)s[m][m]/n;//注意这里是整数除法 printf("%.3lf\n", sqrt(dp(1,1,8,8,n))); return 0; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。