区间dp
区间dp一般都是考虑对于每段区间,最优值都是由几段更小区间的最优值得到,是分治思想的一种应用,
将一个区间问题不断划分为更小的区间直至一个元素组成的区间,枚举他们的组合 ,求合并后的最优值。
道理我都懂,但是做了几题之后,发现最难的一点是要看出这题是个区间dp......
这需要一些转化,需要积累......
然后是要找到阶段、状态和状态转移方程。
洛谷p1880石子合并
环形的区间dp,是一个模版,具体看代码吧......
#include<bits/stdc++.h>
using namespace std;
int n,a[210]={0},s[210][210]={0},ma=0,mi=1000000,sum[210]={0};
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
a[i+n]=a[i];//环形,所以乘二
}
for(int i=1;i<=n*2;i++)sum[i]=sum[i-1]+a[i];//前缀和
for(int i=1;i<=n;i++)//先搜最大值(否则就听取WA声一片啦)i表示合并的区间长度
for(int j=1;j<=n*2;j++)//j表示区间起点
{
int end=j+i-1;if(end>2*n)break;
for(int k=j;k<end;k++)//枚举断开点
s[j][end]=max(s[j][end],s[j][k]+s[k+1][end]+sum[end]-sum[j-1]);
}//sum即这次移动所加值
for(int i=1;i<=n;i++)if(s[i][i+n-1]>ma)ma=s[i][i+n-1];
for(int i=1;i<=n;i++)//最小值
for(int j=1;j<=n*2;j++)
{
int end=j+i-1;if(end>2*n)break;
for(int k=j;k<end;k++)
s[j][end]=min(s[j][end],s[j][k]+s[k+1][end]+sum[end]-sum[j-1]);
}
for(int i=1;i<=n;i++)if(s[i][i+n-1]<mi)mi=s[i][i+n-1];
printf("%d\n%d",mi,ma);
return 0;
}
洛谷p1063能量项链
也是区间dp,思想很相似
#include<bits/stdc++.h>
using namespace std;
long long a[210],t,s[210][210]={0},ma=0,mi=1000000,sum[210]={0};
int main()
{
scanf("%d",&t);
for(int i=1;i<=t;i++)
{
scanf("%d",&a[i]);
a[i+t]=a[i];
}
for(int i=1;i<=t;i++)//依旧枚举区间长度
for(int j=1;j<=t*2;j++)//枚举起始点
{
int end=j+i-1;if(end>2*t)break;
for(int k=j;k<end;k++)//枚举断开点
s[j][end]=max(s[j][end],s[j][k]+s[k+1][end]+a[j]*a[k+1]*a[end+1]);//选择最优解
}
for(int i=1;i<=t;i++)if(s[i][i+t-1]>ma)ma=s[i][i+t-1];
printf("%lld",ma);
return 0;
}
洛谷p1220关路灯
已经关闭的灯一定在一个连续区间里,这很显然。如果a和b是不连续的两盏灯,被灯c分开,那么关了a再去关b的时候可以顺带把c关掉。
然后发现,在关闭了一个连续的区间的灯之后,人一定在端点。
所以用s[i][j][0]表示关第i盏到第j盏路灯后人处于第i盏路灯时的最小值,s[i][j][1]表示人处于第j盏灯时的最小值。
就可以区间dp啦。
#include<bits/stdc++.h>
using namespace std;
int s[60][60][2],n,c;
struct light
{
int p,w;
}l[60];
inline void init()
{
scanf("%d%d",&n,&c);
l[0].w=0;
for(int i=1;i<=n;i++)
{
scanf("%d%d",&l[i].p,&l[i].w);
l[i].w+=l[i-1].w;//前缀和
}
return;
}
inline void dp()
{
for(int i=1;i<=n;i++)
s[i][i][0]=s[i][i][1]=l[n].w*abs(l[i].p-l[c].p);//初始值
for(int len=2;len<=n;len++)
for(int i=1;i<=n-len+1;i++)
{
int j=i+len-1;//最后关第i盏灯则之前已关第i+1至第j盏灯,最后关第j盏灯反之
s[i][j][0]=min(s[i+1][j][0]+abs(l[i+1].p-l[i].p)*(l[i].w+l[n].w-l[j].w),//加上的为关灯途中没关的灯所耗的电能
s[i+1][j][1]+abs(l[j].p-l[i].p)*(l[i].w+l[n].w-l[j].w));
s[i][j][1]=min(s[i][j-1][1]+abs(l[j].p-l[j-1].p)*(l[i-1].w+l[n].w-l[j-1].w),
s[i][j-1][0]+abs(l[j].p-l[i].p)*(l[i-1].w+l[n].w-l[j-1].w));
}
return;
}
int main()
{
init();
dp();
if(s[1][n][0]<s[1][n][1])
printf("%d",s[1][n][0]);
else printf("%d",s[1][n][1]);
return 0;
}
洛谷p1622释放囚犯
正着做死活做不出,参考题解中所说,将这个过程反过来,把要释放的人视作断点,将p个人分成q+1个区间,求合并区间至一个区间所需最小值,即可视为合并石子,突然通悟......
#include<bits/stdc++.h>
using namespace std;
int s[110][110]={0},p,q,a[110]={0},sum[110]={0};
inline void init()
{
scanf("%d%d",&p,&q);
for(int i=1;i<=q;i++)scanf("%d",&a[i]);
a[0]=0;a[++q]=p+1;
sort(a,a+q+1);
return;
}
int main()
{
init();
for(int i=1;i<=q;i++)
sum[i]=a[i]-a[i-1]-1+sum[i-1];//前缀和,将问题转换为求几堆石子合并的最小值
for(int k=2;k<=q;k++)
for(int i=1;i<=q-k+1;i++)
{
int j=i+k-1;
for(int p=i;p<j;p++)
if(!s[i][j]||s[i][j]>s[i][p]+s[p+1][j]+sum[j]-sum[i-1]+j-i-1)//注意j-i+1,是指合并时几个还未释放的人
s[i][j]=s[i][p]+s[p+1][j]+sum[j]-sum[i-1]+j-i-1;
}
printf("%d",s[1][q]);
return 0;
}
洛谷p3205合唱队伍
先将问题转化为求排成末状态的不同的方案数。
对于最终序列的一个区间[i,j],最后一个添加的只可能是第i个人或者第j个人。如果最后一个是i,则对于这个区间,a[i]<a[i+1]或者a[i]<a[j]。如果最后一个是j,则对于这个区间a[j]>a[j-1]或者a[j]>a[i]。
#include<bits/stdc++.h>
using namespace std;
int n,s[1010][1010][2]={0},a[1010]={0};s[i][j][0]表示排出[i,j]且最后一个人从左边排入的方案数,s[i][j][1]表示最后一人从右边排入
const int mod=19650827;
inline void init()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)s[i][i][0]=1;//s[i][i][1]不能也存1,否则结果会double
return;
}
inline void dp()
{
for(int len=2;len<=n;len++)
for(int i=1;i<=n-len+1;i++)
{
int j=i+len-1;
if(a[i]<a[i+1])s[i][j][0]+=s[i+1][j][0];//如果可从左侧排入人
if(a[i]<a[j])s[i][j][0]+=s[i+1][j][1];
if(a[j]>a[i])s[i][j][1]+=s[i][j-1][0];//如果可从右侧排入人
if(a[j]>a[j-1])s[i][j][1]+=s[i][j-1][1];
s[i][j][0]%=mod;
s[i][j][1]%=mod;
}
return;
}
int main()
{
init();
dp();
printf("%d",(s[1][n][0]+s[1][n][1])%mod);
return 0;
}