最近作死的新开一个群每天一个a+b,群主是不是得坚持下来呢,感谢群巨的鼓励和支持,一起加油


区间DP:HDOJ5115

区间DP特点总结:

数据量小,n一般在100,200左右,因为一般需要三个循环,太大了循环不起来,会超时

一般dp【1】【n】为结果

dp【i】【j】的状态转移需要中间量k作为转移的中点,dp【i】【k】,dp【k】【j】为最优子结构


题意:n匹狼,每匹狼有自身的伤害值a【i】,会给相邻的两匹狼b【i】的助攻值,即如果想打死第二匹,需要花费a【2】+b【1】+b【3】

求打死n匹狼至少承担多少伤害?


典型区间dp,dp【1】【n】为最终答案

方法一般有两种:记忆化的DP和三重for循环的

个人推荐记忆化是因为不需要关注变量的循环顺序和大小,只要知道最优子结构如果推导下一层就可以

for循环也有好处:每个子区间肯定只会计算一次,记忆化会要迭代很多次,容易超时


在这个题中,dp【i】【j】的转移是:在区间【i,j】中,最后打死的是哪匹狼会获得最小的伤害

如果是最后打死i,那么花费是dp【i+1】【j】+a【i】+b【i-1】+b【j+1】

如果是最后打死j,那么花费是dp【i】【j-1】+a【j】+b【i-1】+b【j+1】

如果是最后打死k,i<k<j,那么花费是dp【i】【k-1】+dp【k+1】【j】+a【k】+b【i-1】+b【j+1】

取最小即可。细节点就是记得初始化


记忆化代码

int t,n;
int a[maxn];
int b[maxn];
int dp[maxn][maxn];

int getdp(int i,int j){
	if (dp[i][j]!=-1) return dp[i][j];
	if (i==j) return dp[i][j]=a[i]+b[i-1]+b[i+1];
	int tmp1=getdp(i,j-1)+a[j]+b[i-1]+b[j+1];
	int tmp2=getdp(i+1,j)+a[i]+b[i-1]+b[j+1];
	int tmp,ans=min(tmp1,tmp2);
	for(int k=i+1;k<j;k++){
		tmp=getdp(i,k-1)+getdp(k+1,j)+a[k]+b[i-1]+b[j+1];
		ans=min(ans,tmp);
	}
	return dp[i][j]=ans;
}

int main(){
	//input;
	scanf("%d",&t);
	for(int Case=1;Case<=t;Case++){
		scanf("%d",&n);
		for(int i=1;i<=n;i++) scanf("%d",&a[i]);
		for(int i=1;i<=n;i++) scanf("%d",&b[i]);
		memset(dp,-1,sizeof(dp));
		printf("Case #%d: %d\n",Case,getdp(1,n));
	}
	return 0;
}

for循环代码:

long long dp[maxn][maxn];
long long a[maxn];
long long b[maxn];
int n,t;

void debug(){
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
        printf("%I64d%c",dp[i][j],j==n?'\n':' ');
}

int main(){
    //input;
    int i,j,k,len;
    scanf("%d",&t);
    for(int Case=1;Case<=t;Case++){
        scanf("%d",&n);
        memset(a,0,sizeof(a));
        memset(b,0,sizeof(b));
        for(i=1;i<=n;i++) scanf("%lld",&a[i]);
        for(i=1;i<=n;i++) scanf("%lld",&b[i]);
        for(i=1;i<=n;i++)
        for(j=i+1;j<=n;j++)
            dp[i][j]=INF;
        for(i=1;i<=n;i++)
            dp[i][i]=a[i]+b[i-1]+b[i+1];
        for(i=1;i<=n-1;i++)
            dp[i][i+1]=min(dp[i][i]+a[i+1]+b[i-1]+b[i+2],dp[i+1][i+1]+a[i]+b[i-1]+b[i+2]);
        //debug();
        for(i=n-2;i>=1;i--)
        for(len=3;len+i<=n+1;len++){
            j=i+len-1;
            //dp[i][j]=min(dp[i][j],dp[i+1][j]+a[i]+b[i-1]+b[j+1]);
            //dp[i][j]=min(dp[i][j],dp[i][j-1]+a[j]+b[i-1]+b[j+1]);
            for(k=i;k<=j;k++)
                dp[i][j]=min(dp[i][j],dp[i][k-1]+dp[k+1][j]+a[k]+b[i-1]+b[j+1]);
        }
        //debug();
        printf("Case #%d: %lld\n",Case,dp[1][n]);
    }
    return 0;
}

个人习惯:

i作为区间起点,然后枚举的是区间长度len,用起点和长度来计算终点j

然后枚举分隔地点k

之后的东西就一样了