https://www.acwing.com/problem/content/284/
#include
#include
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
#include
#include
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
集合还是以分界线的形式划分
线性做法:
环形做法:
这题是把一个珠子断开,首位有相同的珠子
有n个珠子,有n种断开方式
和上一题差不多
#include
#include
#include
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
#include
#include
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
#include
#include
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
#include
using namespace std;
#include
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
#include
using namespace std;
//二维前缀和 + 记忆化搜索(状态表示太长了,有5维)
#include
#include
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;
}