一般是由长度小的子问题推到长度大的子问题,解法一般比较固定,先枚举长度再枚举左端点 最后枚举中间的分割点

通过小区间的情况扩展到大区间

1、合并石子1

线性的,合并相邻的,通过计算前缀和减少了计算量,但也是O(N^3)

cin>>n;
    for(int i=1;i<=n;i++) {
        cin>>a[i];
        s[i]=s[i-1]+a[i];
    }
    memset(f,127/3,sizeof(f));
    for(int i=1;i<=n;i++) f[i][i]=0;  //赋一个极大的值但是相加不会溢出
    for(int i=n-1;i>=1;i--){
        for(int j=i+1;j<=n;j++){
            for(int k=i;k<=j-1;k++){
                if(f[i][j]>f[i][k]+f[k+1][j]+s[j]-s[i-1]) //把i--k和k+1--j这两堆合并起来,原本的得分加上现在的新增的得分
                f[i][j]=f[i][k]+f[k+1][j]+s[j]-s[i-1];
            }
        }
    }
    cout<<f[1][n]<<endl;

2、合并石子2

题解:https://www.luogu.com.cn/problem/P1880

在一个圆形操场的四周摆放 NN 堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

试设计出一个算法,计算出将 NN 堆石子合并成 11 堆的最小得分和最大得分。

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
//区间DP
//把环形化成线性 
//合并石子这道题:很典型的区间dp,通过求解子区间,然后按照合并为大区间
int w[221],dp1[220][220];  //存储区间i--j 
int a[220]; 
int n;
int dp2[220][220];
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		a[i+n]=a[i];  
		w[i]=w[i-1]+a[i];  //前缀和减少计算 
	} 
	for(int i=n+1;i<=2*n;i++) w[i]=w[i-1]+a[i];
	for(int len=1;len<n;len++){
		for(int i=1,j=i+len;i<(n*2)&&j<(2*n);i++,j=i+len){
			dp1[i][j]=INF;
			dp2[i][j]=-1;
			for(int k=i;k<j;k++){  //中间元素 
				dp1[i][j]=min(dp1[i][j],dp1[i][k]+dp1[k+1][j]+w[j]-w[i]+a[i]);
				dp2[i][j]=max(dp2[i][j],dp2[i][k]+dp2[k+1][j]+w[j]-w[i]+a[i]);
			}
		}
	}
	int maxx=-1,minn=INF;
	for(int i=1;i<=n;i++){
		maxx=max(maxx,dp2[i][i+n-1]);
		minn=min(minn,dp1[i][i+n-1]);
	}
	cout<<minn<<endl<<maxx<<endl;
return 0;
}

通过四边形不等式进行优化,时间复杂度下降至O(N^2),因为保存上一次的最佳分割点,证明自己找资料再看看:

for(int k=rel[i][j-1];k<=rel[i+1][j];k++)

以合并石子2为例

//经过四边形不等式优化的合并石子
int rel[maxn][maxn];
int dp[maxn][maxn]; 
int summ[maxn];
int a[maxn];
int n;
int main(){
	cin>>n;
	memset(summ,0,sizeof(summ));
    memset(dp,0x3f,sizeof(dp));   //别忘了初始化 
	for(int i=1;i<=n;i++){
		cin>>a[i];
		dp[i][i]=0;
		rel[i][i]=i;
		summ[i]=summ[i-1]+a[i];
	}
	for(int i=1;i<=n;i++){
		summ[i+n]=summ[i+n-1]+a[i];
		dp[i+n][i+n]=0;
		rel[i+n][i+n]=i+n;
	}
	for(int len=1;len<=n;len++){
		for(int i=1;i<=2*n-len;i++){
			int j=i+len-1;
			for(int k=rel[i][j-1];k<=rel[i+1][j];k++){
				if(dp[i][j]>dp[i][k]+dp[k+1][j]+summ[j]-summ[i-1]) {
					dp[i][j]=dp[i][k]+dp[k+1][j]+summ[j]-summ[i-1];
					rel[i][j]=k; 
				}
				
			}
		}
	}
	int ans= 0xfffffff;
	for(int i=1;i<=n;i++){
		ans=min(ans,dp[i][i+n-1]);
	}
	cout<<ans<<endl;
return 0;
} 

1570:【例 2】能量项链

这个其实就是矩阵相乘,一样的转移表达式

注意要拆分乘2*N,而且这个的循环一二维循环的是区间左右下标,注意转移表达式

dp[j][i]=max(dp[j][i],dp[j][k]+dp[k+1][i]+a[j]*a[k+1]*a[i+1]);

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
typedef long long LL;
//能量项链
//矩阵相乘提高版,或者题目也可以说成是抽卡片,抽卡片更好理解
//f[i][j]=min(f[i][k]+f[k][j]+a[i]?a[k]?a[j],f[i][j])
int n;
LL a[220];
LL dp[220][220];

int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
		dp[i][i]=0;
		a[i+n]=a[i];
	}
	LL ans=-1;
	for(int i=2;i<n*2;i++){  //整个区间都需要更新 
		for(int j=i-1;i-j<n&&j>=1;j--){  //从后往前推 
			for(int k=j;k<i;k++){
				dp[j][i]=max(dp[j][i],dp[j][k]+dp[k+1][i]+a[j]*a[k+1]*a[i+1]);
			//	dp[j][i]=max(dp[j][i],dp[j][k]+dp[k+1][i]+a[j-1]*a[k]*a[i]);
			}
			ans=max(ans,dp[j][i]);
		}
	}
	printf("%lld",ans);
return 0;
}

1571:【例 3】凸多边形的划分

和上一道题的转移式比较像,但是这道题数据大, 要写高精度

dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+a[i]*a[j]*a[k]);

看了另一个题解,发现不用写高精度(36条消息) 1571 例题3 凸多边形的划分(LOJ10149) 区间动规思想40分 区间动归 dp 两定点一动点50分 int128使用100分 uint128使用100分_mrcrack的博客-CSDN博客

uint128使用

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
typedef long long LL;
//	dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+a[i]*a[j]*a[k]);
//但是需要写高精度
/* 
int n;
LL a[51];
LL dp[51][51];

int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
	}
	LL ans=INF;
	for(int len=1;len<=n;len++){
		for(int i=1;i<=n-len+1;i++){
			dp[i][i+len]=INF;
			dp[i][i+1]=0;  //不能形成
			int j=i+len-1;
			for(int k=i;k<=i+len-1;k++){
			dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+a[i]*a[j]*a[k]);	
			} 
			if(len==n) ans=min(ans,dp[i][i+n-1]);
		}
		
	}
	printf("%lld",ans);
return 0;
}
*/
inline void qread(int &x){
	x=0;
	int ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
struct bignum{
	int num[48];
	void sset(int x){
		memset(num,0,sizeof(num));
		while(x){
			num[++num[0]]=x%10;
			x/=10;
		}
	}
	void clea(){
		memset(num,0,sizeof(num));
	}
	void INf(){
		num[0]=30;
		for(int i=1;i<=num[0];i++) num[i]=9;
	}
};
bignum minn(const bignum &a,const bignum &b){
	if(a.num[0]>b.num[0]) return b;
	if(a.num[0]<b.num[0]) return a;
	for(int i=a.num[0];i>=1;i--) {
		if(a.num[i]>b.num[i]) return b;
		if(a.num[i]<b.num[i]) return a;
	}
	return a;
}
bignum add(const bignum &a,const bignum &b){
	bignum c;
	c.clea();
	c.num[0]=max(a.num[0],b.num[0]);
	int jw=0;
	for(int i=1;i<=c.num[0];i++){
		c.num[i]=a.num[i]+b.num[i]+jw;
		jw=c.num[i]/10;
		c.num[i]%=10;
	}
	if(jw) c.num[++c.num[0]]=jw;
	return c;
}
bignum mul(bignum a,bignum b){
	bignum c;
	c.clea();
	c.num[0]=a.num[0]+b.num[0];  //相乘 
	for(int i=1;i<=a.num[0];i++){
		int jw=0;
		for(int j=1;j<=b.num[0];j++){
			c.num[i+j-1]+=a.num[i]*b.num[j]+jw;
			jw=c.num[i+j-1]/10;
			c.num[i+j-1]%=10;
		}
		c.num[i+b.num[0]]=jw;
	}
	while(c.num[c.num[0]]==0) --c.num[0];
	if(!c.num[0]) c.num[0]=1;
	return c;
}
void show(const bignum &a){
	for(int i=a.num[0];i>=1;i--) putchar(a.num[i]+48);
}
int n;
bignum data[120];
bignum dp[120][120];
int main(){
	qread(n);
	int x;
	for(int i=1;i<=n;i++){
		qread(x);
		data[i].sset(x);
		data[i+n]=data[i];
		
	}
	data[n<<1|1]=data[1];
	for(int len=2;len<n;len++){   //格式 
		for(int i=1;i<=(n<<1)-len;i++){
			int j=i+len;
			dp[i][j].INf();
			for(int k=i+1;k<j;k++)
				dp[i][j]=minn(dp[i][j],add(dp[i][k],add(dp[k][j],mul(data[i],mul(data[k],data[j])))));
		}
		
	}
	bignum ans;
		ans.INf();
		for(int i=1;i<=n;i++) ans=minn(ans,dp[i][i+n-1]);  //最后取值 
	show(ans);
	cout<<endl;
	return 0;
} 
#include <bits/stdc++.h>
#define maxn 110
#define INF 1e30
using namespace std;
__uint128_t a[maxn],dp[maxn][maxn],mx;
inline __uint128_t read(){
	__uint128_t x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9'){//去除其他无用符号 
		ch=getchar();
	}
	while('0'<=ch&&ch<='9'){//读取数字 
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x;
}
inline void print(__uint128_t x){
	if(x){//请注意此处是if不是while 
		print(x/10);//自高位到低位输出,采用递归形式 
		putchar(x%10+'0');//以字符形式打印 
	}
}
int main(){
	int n,lt,rt,k,len,i,j;
	scanf("%d",&n);
	for(i=1;i<=n;i++)a[i]=read(),a[i+n]=a[i];
	for(i=1;i<=2*n;i++)
		for(j=i+2;j<=2*n;j++)
			dp[i][j]=INF;
	for(len=2;len<n;len++)
		for(lt=1;lt<=2*n;lt++){//三角形,三个顶点 
			rt=lt+len;//lt,rt是定点 
			if(rt>2*n)break;
			for(k=1;k<len;k++){//lt+k是动点,注意k取值从1开始 
				dp[lt][rt]=min(dp[lt][rt],dp[lt][lt+k]+dp[lt+k][rt]+a[lt]*a[lt+k]*a[rt]);
			}
		}
	mx=INF;
	for(i=1;i<=n;i++)mx=min(mx,dp[i][i+n-1]);
	print(mx);
	return 0;
}

1572:括号配对

 求最少增加多少个符号?

其实可以求能够匹配的有多少,然后总长度减去匹配的,一样的枚举长度len,i起始点,j终止点,k中间点

如果s[i]、s[j]匹配那么就是,f[i][j]=max(f[i+1][j-1]+2,f[i][j])

否则就是f[i][j]=max(f[i][j],f[i][k]+f[k+1][j])

#include <bits/stdc++.h>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
typedef long long LL;
int dp[105][105];
int main(){
	
	char s[110];
	scanf("%s",s+1);
	int len=strlen(s+1);
	for(int l=1;l<=len;l++){
		for(int i=1;i<=len-l+1;i++){
			int j=i+l-1;
			if((s[i]=='('&&s[j]==')')||(s[i]=='['&&s[j]==']'))
				dp[i][j]=max(dp[i+1][j-1]+2,dp[i][j]);
			for(int k=i;k<j;k++){
				dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]);
			}
		}
	}
	cout<<len-dp[1][len];
	return 0;
}

 

1573:分离与合体

表达转移式变化了,因为题目的要求,但是做法还是一样,而且这道题还需要输出对策,

合并时获得的价值就是 (1 号金钥匙价值 <span class="MJX_Assistive_MathML">+3 号金钥匙价值)&times;(&nbsp;<span class="MathJax_Preview"><span id="MathJax-Element-18-Frame" class="MathJax" data-mathml="&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/math&gt;"><span id="MathJax-Span-71" class="math"><span class="MJX_Assistive_MathML">2 号金钥匙价值))。

LYD 请你编程求出最终可以获得的最大总价值,并按照分离阶段从前到后,区域从左到右的顺序,输出发生分离区域编号。若有多种方案,选择分离区域尽量靠左的方案(也可以理解为输出字典序最小的)。

例如先打印一分为二的区域,然后从左到右打印二分为四的分离区域,然后是四分为八的……

/*
大意就是选取区间一点区间划分为左右部分,合并时得分为划分前(左端点+右端点) * 选取点的权值。
我们还是按照老办法,用跨度来 DP 就可以得到最大得分。
关于路径输出,因为本题中区间划分是同时进行的,就是说如果存在多段区间都可划分,那么它们是可以同时划分的,而题意又要求按照划分的先后和区域的左右输出,
也就是说要先判断该节点是属于第几次划分的,由于我们 path 数组存放了区间的划分点,那么我们不断递归,递归树第 k 层就对应着第 k 次划分,而左右顺序取决于我们的递归顺序;
因此我们可以通过传入变量 step 代表当前递归树的层数,num 代表当前要输出的是第几步划分出的区域,当 step = num 就说明满足条件。
*/

所以这个DFS(pint函数比较重要)

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=303;
const int INF=0x3fffffff;
typedef long long LL;
//重点是理解题意 
/*
大意就是选取区间一点区间划分为左右部分,合并时得分为划分前(左端点+右端点) * 选取点的权值。
我们还是按照老办法,用跨度来 DP 就可以得到最大得分。
关于路径输出,因为本题中区间划分是同时进行的,就是说如果存在多段区间都可划分,那么它们是可以同时划分的,而题意又要求按照划分的先后和区域的左右输出,
也就是说要先判断该节点是属于第几次划分的,由于我们 path 数组存放了区间的划分点,那么我们不断递归,递归树第 k 层就对应着第 k 次划分,而左右顺序取决于我们的递归顺序;
因此我们可以通过传入变量 step 代表当前递归树的层数,num 代表当前要输出的是第几步划分出的区域,当 step = num 就说明满足条件。
*/
int n;
LL a[maxn],dp[maxn][maxn];
int path[maxn][maxn];
void prin(int i,int j,int step,int num){ //step 代表当前递归树的层数,num 代表当前要输出的是第几步划分出的区域 
//num从1增加到n表示,从一分为二,二分为四这个顺序
//step就是一直都是1,(调用时) 
	if(i>=j) return;
	if(step==num) cout<<path[i][j]<<" ";
	prin(i,path[i][j],step+1,num);
	prin(path[i][j]+1,j,step+1,num);
}
void solve(){
	for(int len=1;len<n;len++){  //这里还是模板 
		for(int i=1;i<=n-len;i++){
			int j=i+len;
			for(int k=i;k<j;k++){
				if(dp[i][j]<dp[i][k]+dp[k+1][j]+(a[i]+a[j])*a[k]){
					dp[i][j]=dp[i][k]+dp[k+1][j]+(a[i]+a[j])*a[k]; //k是分割点 
					path[i][j]=k;
				}
			}
		}
	}
	LL mx=dp[1][n];
	cout<<mx<<endl;
	for(int i=1;i<=n;i++) prin(1,n,1,i);
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	solve();
return 0;
}

1574:矩阵取数游戏

每一行是不会互相影响的
所以只需要对每一行算出最优,然后相加就可以了
记得要用高精,
用M表示输入的矩阵的一行,M[i]表示该行第i个值
dp[i][j]表示区间变为[i,j]时的最优解
状态转移方程为:dp[i][j]=max(dp[i-1][j]+M[i-1]* 2^{m-j+i-1} ,dp[i][j+1]+M[j+1]*2^{m-j+i-1})
从两边向中间靠拢
终态是区间为空,dp[i][i]+M[i]*2^m(从1~n遍历,取最大)

!!!这道题也需要写高精度,这个真的需要练习

//怎么连最简单的一点都没想到呢,每一行是不会互相影响的
//所以只需要对每一行算出最优,然后相加就可以了
//记得要用高精,
//用M表示输入的矩阵的一行,M[i]表示该行第i个值
//dp[i][j]表示区间变为[i,j]时的最优解
//状态转移方程为:dp[i][j]=max(dp[i-1][j]+M[i-1]* 2^{m-j+i-1} ,dp[i][j+1]+M[j+1]*2^{m-j+i-1})
//从两边向中间靠拢 
//终态是区间为空,dp[i][i]+M[i]*2^m
/*
int n,m,a[maxn];
LL f[maxn][maxn];  //f[i][j]之间的最优值 
void prin(LL x){
	if(x>9) prin(x/10);
	putchar(x%10+'0');
} 
LL solve(){
	memset(f,0,sizeof(f));
	for(int i=1;i<=m;i++){  //从两边开始 
		for(int j=m;j>=i;j--){
			LL b=(LL)1<<(m-j+i-1);
			f[i][j]=max(f[i-1][j]+(LL)a[i-1]*b,f[i][j+1]+(LL)a[j+1]*b);
		}
	}
	LL mx=-1;
	//最后一步 
	for(int i=1;i<=m;i++) mx=max(mx,f[i][i]+(LL)a[i]*((LL)1<<m)); //取最大 
	return mx;
}
int main(){
	scanf("%d %d",&n,&m);
	LL ans=0;
	while(n--){
		for(int i=1;i<=m;i++) scanf("%d",&a[i]);
		ans+=solve();
	}
	prin(ans);
return 0;
}
*/


//老老实实写高精度TAT  4个点过不了
#include <bits/stdc++.h>
using namespace std;
const int N=85;
const int L=105,Power=4,Base=10000;
int n,m,a[N];
struct Bignum
{
    int a[L];
    Bignum(){memset(a,0,sizeof a);}
    Bignum(int x)
    {
        memset(a,0,sizeof a);
        while(x) {a[++*a]=x%10; x/=10;}
        return;
    }
    inline void Print()
    {
        int i;
        printf("%d",a[*a]);
        for(i=*a-1;i>=1;i--)
        {
            if(a[i]<1000) putchar('0');
            if(a[i]<100) putchar('0');
            if(a[i]<10) putchar('0');
            printf("%d",a[i]);
        }
        puts("");
        return;
    }
    inline void Init()
    {
        memset(a,0,sizeof a);
    }
}Bin[N],dp[N][N];
inline bool operator<(const Bignum &p,const Bignum &q)
{
    if(p.a[0]!=q.a[0]) return (p.a[0]<q.a[0])?1:0;
    int i;
    for(i=p.a[0];i>=1;i--) if(p.a[i]!=q.a[i])
    {
        return (p.a[i]<q.a[i])?1:0;
    }
    return 0;
}
inline Bignum max(Bignum p,Bignum q)
{
    return (p<q)?(q):(p);
}
inline Bignum operator+(const Bignum &p,const Bignum &q)
{
    int i;
    Bignum ans=p;
    for(i=1;i<=q.a[0];i++)
    {
        ans.a[i]+=q.a[i];
        if(ans.a[i]>=Base){ans.a[i+1]+=ans.a[i]/Base; ans.a[i]%=Base;}
    }
    while(ans.a[ans.a[0]+1]) ans.a[0]++;
    return ans;
}
inline Bignum operator*(const Bignum &p,const Bignum &q)
{
    int i,j;
    Bignum ans;
    ans.a[0]=p.a[0]+q.a[0];
    for(i=1;i<=p.a[0];i++)
    {
        for(j=1;j<=q.a[0];j++)
        {
            ans.a[i+j-1]+=p.a[i]*q.a[j];
            if(ans.a[i+j-1]>Base)
            {
                ans.a[i+j]+=ans.a[i+j-1]/Base;
                ans.a[i+j-1]%=Base;
            }
        }
    }
    while(!ans.a[ans.a[0]]) ans.a[0]--;
    return ans;
}
inline Bignum operator*(const Bignum &p,const int &q)
{
    int i;
    Bignum ans;
    ans.a[0]=p.a[0]+5;
    for(i=1;i<=p.a[0];i++)
    {
        ans.a[i]+=p.a[i]*q;
        if(ans.a[i]>Base)
        {
            ans.a[i+1]+=ans.a[i]/Base; ans.a[i]%=Base;
        }
    }
    while(!ans.a[ans.a[0]]) ans.a[0]--;
    return ans;
}
inline Bignum Solve()
{
    int i,j;
    for(i=1;i<=m;i++)
    {
        for(j=1;j+i-1<=m;j++)
        {
            int l=j,r=j+i-1;
            dp[l][r]=max(dp[l][r-1]+Bin[m-i+1]*a[r],dp[l+1][r]+Bin[m-i+1]*a[l]);
        }
    }
//    dp[1][m].Print();
    return dp[1][m];
}
int main()
{
    int i,j;
    Bignum ans;
    scanf("%d%d",&n,&m);
    Bin[0]=Bignum(1);
    for(i=1;i<=m;i++) Bin[i]=Bin[i-1]*2;
//    for(i=1;i<=m;i++)
//    {
//        Bin[i].Print();
//    }
//    puts("");
    for(i=1;i<=n;i++)
    {
        for(j=1;j<=m;j++) scanf("%d",&a[j]);
        ans=ans+Solve();
    }
    ans.Print();
    return 0;
}

  

 

凸包的路径

这道题将环形的信息转化为了长度为2倍的链,就变成了线性的问题

而且这道题。。。我觉得我不是很懂

CF838E Convex Countour

按顺时针给出一个凸多边形,任意两点间有一条直线边,求每个点恰好一次的最长不自交(和之前线段相交)路径

题解:https://www.luogu.com.cn/problemnew/solution/CF838E

//https://www.luogu.com.cn/problemnew/solution/CF838E 
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;

const int N=2510;
struct Point {
    double x, y;
    Point(int x=0, int y=0):x(x), y(y){}
} p[N];
double dis(Point a, Point b) {
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
double f[N][N][2];
int n;

int main() {
    scanf("%d", &n);
    for (int i=0; i<n; i++) {
        int x, y; scanf("%d%d", &x, &y);
        p[i]=Point(x, y);
    }
    for (int len=2; len<=n; len++)
        for (int l=0; l<n; l++) {
            int r=(l+len-1)%n;
            f[l][r][0]=max(f[(l+1)%n][r][0]+dis(p[l], p[(l+1)%n]), f[(l+1)%n][r][1]+dis(p[l], p[r]));
            f[l][r][1]=max(f[l][(r-1+n)%n][0]+dis(p[r], p[l]), f[l][(r-1+n)%n][1]+dis(p[r], p[(r-1+n)%n]));
        }
    double ans=0;
    for (int i=0; i<n; i++) ans=max(ans, max(f[i][(i+n-1)%n][0], f[i][(i+n-1)%n][1]));
    printf("%.10lf", ans);
    return 0;
}

  

4、矩阵相乘提高版,或者题目也可以说成是抽卡片,抽卡片更好理解

给出n个数字,不能删除两边,删除i的代价是a[i-1]*a[i]*a[i+1]

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
long long a[110],dp[110][110];
//区间dp
//dp[i][j]=min(dp[i][k]+dp[k+1][j]+a[i-1][k][k+1] 
int n; 

int main(){
	cin>>n;
	int x;   
 	for(int i=0;i<=n;i++){
		cin>>a[i];
		dp[i][i]=0;
		if(i!=0&&i!=n) cin>>x; //根据转移表达式,有一个不用输入 
 	}
	for(int len=2;len<=n;len++){
		for(int i=1;i<=n-len+1;i++){
			int j=i+len-1;
			long long temp=dp[i+1][j]+a[i-1]*a[i]*a[j];
			for(int k=i+1;k<j;k++){
				long long t=dp[i][k]+dp[k+1][j]+a[i-1]*a[k]*a[j];
				temp=min(temp,t);
			}
			dp[i][j]=temp;
		}
	}
	cout<<dp[1][n]<<endl;
return 0;
}

5、添加括号

【题目背景】
  给定一个正整数序列a1,a2,...,an,(1<=n<=20)
  不改变序列中每个元素在序列中的位置,把它们相加,并用括号记每次加法所得的和,称为中间和。
  例如:
  给出序列是4,1,2,3。
  第一种添括号方法:
    ((4+1)+(2+3))=((5)+(5))=(10)
  有三个中间和是5,5,10,它们之和为:5+5+10=20
  第二种添括号方法 
    (4+((1+2)+3))=(4+((3)+3))=(4+(6))=(10)
  中间和是3,6,10,它们之和为19。 
【问题描述】 
  现在要添上n-1对括号,加法运算依括号顺序进行,得到n-1个中间和,求出使中间和之和最小的添括号方法。 

const int maxn=1010;
const int INF=0x3fffffff;
//区间dp:但是涉及三个问题
//第一个:找到最优值,这个就是转台转移表达式
//首先是dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]) dp[i][j]+=sum(i,j)
//然后是输出括号:记录每个数字前面有多少个左括号,后面有多少个右括号
//最后是输出中间值
//后面两个问题都是通过DFS来解决,当然是在第一个问题通过DP解决了,并且记录了最优值得基础上
int a[21],summ[21][21]; //注意理解这个summ数组,是一个斜三角形,summ[i][j]就代表了从i到j得和,因为第i行就是从第i-1开始的 
int dp[21][21]; //最小值
int be[21],af[21]; // 记录每个数字前面有多少个左括号,后面有多少个右括号
int n;
int res[21][21];
void dfs(int l,int r){
	if(l==r) return ;
	dfs(l,l+res[l][r]); //递归
	dfs(l+res[l][r]+1,r);
	be[l]++;
	af[r]++ ;
}
void dfs2(int l,int r){
	if(l==r) return;
	dfs2(l,l+res[l][r]);
	dfs2(l+res[l][r]+1,r);
	cout<<summ[l][r]<<" "; //最后输出 
}

int main(){
	cin>>n;
	memset(dp,0x3f,sizeof(dp));
	for(int i=1;i<=n;i++){
		cin>>a[i];
		dp[i][i]=0;
	}
	for(int i=1;i<=n;i++){
		for(int j=i;j<=n;j++) summ[i][j]=summ[i][j-1]+a[j]; //初始summ数组 
	}
	for(int len=1;len<n;len++){
		for(int i=1;i<=n-len;i++){
			int j=i+len;
			for(int step=0;step<len;step++){  //记录 
				if(dp[i][j]>dp[i][i+step]+dp[i+step+1][j]){ 
					dp[i][j]=dp[i][i+step]+dp[i+step+1][j];
					res[i][j]=step;  //i和j之间的括号在那个位置 
				}
			}
			dp[i][j]+=summ[i][j] ;// 还要加上和 
		}
	}
	dfs(1,n);
	//记录左右括号 
	for(int i=1;i<=n;i++){
		for(int j=1;j<=be[i];j++) cout<<"("; //前面有多少个左括号
		cout<<a[i];
		for(int j=1;j<=af[i];j++) cout<<")";
		if(i!=n) cout<<"+"; 
	} 
	cout<<endl<<dp[1][n]<<endl;
	dfs2(1,n);
return 0;
}

6、最长括号匹配   Poj2955 括号匹配(一)

dp[i][j]表示i~j个字符间的最长匹配

if(a[i]==a[j]) dp[i][j]=dp[i+1][j-1]+2

dp[i][j]=max{dp[i][j],dp[i][k]+dp[k+1][j]}

int dp[105][105];
int main()
{
    char s[105];
    while(scanf("%s",s+1)!=EOF)
    {
        memset(dp,0,sizeof(dp));//dp初始化为0,因为一方面是找最大之,一方面初始匹配数为0
        int len = strlen(s+1);//dp[i][i]不用处理,因为自己和自己不匹配就是0
        if(s[1]=='e')break;
        for(int l = 1;l<=len;l++){
            for(int i = 1;i+l<=len+1;i++){
                int j= i+l-1;
                if((s[i]=='('&&s[j]==')')||(s[i]=='['&&s[j]==']')){//如果匹配,先更新
                    dp[i][j] = dp[i+1][j-1]+2;
                }
                for(int k = i;k<j;k++){//k<j
                    dp[i][j] = max(dp[i][j],dp[i][k]+dp[k+1][j]);
                }
            }
        }
        cout<<dp[1][len]<<endl;
    }
    return 0;
}

7、hdu 4632 Palindrome subsequence

 最多回文子串,要求不连续,位置不同就可以了

先全部都处理:dp[i][j]=dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1]   这个操作是并上,有容斥原理可以得到,而且这个是一个固定的状态,注意要取模,所以要先加模,再取模

如果s[i]==s[j]的话,那么就可以分为dp[i][j]=dp[i][j]+dp[i+1][j-1]+1,

//最多回文子串
//先全部都处理:dp[i][j]=dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1]
//如果s[i]==s[j]的话,那么就可以分为dp[i][j]=dp[i][j]+dp[i+1][j-1]+1
char a[maxn]; 
int n;
int dp[maxn][maxn];
int main(){
	scanf("%d",&n);
	int index=1;
	while(n--){
		scanf("%s",a+1);

		int nn=strlen(a+1);
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=nn;i++) dp[i][i]=0;
		for(int len=1;len<=nn;len++){
			for(int i=1;i<=nn-len+1;i++){
				int j=i+len-1;
				dp[i][j]=(dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1]+10007)%10007;
				if(a[i]==a[j]) dp[i][j]=(dp[i][j]+dp[i+1][j-1]+1)%10007;
			}
		}
		printf("Case %d: %d\n",index++,dp[1][nn]);
	}
	
	
return 0;
}

8、整数划分

给出两个整数 n , m ,要求在 n 中加入m - 1 个乘号,将n分成m段,求出这m段的最大乘积

这里给的乘号是有限个,所以状态方程里必须包含使用乘号的个数,此外还要包含区间长度。所以怎么用二维dp实现包含m和n,我们可以用dp[i][j]表示在第1~i个字符里插入j个乘号的最大值。

状态转移方程 dp[i][j]表示在第1i个字符里插入j个乘号的最大值;用num[i][j]表示第ij个字符表示的数字;

dp[i][j] = max(dp[i][j],dp[k][j-1]*num[k+1][i])

 

 

 

 

 

 posted on 2020-02-11 18:56  shirlybabyyy  阅读(273)  评论(0编辑  收藏  举报