区间dp题解

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_43298454/article/details/97516155

[NOI1995]石子合并

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

题解:
dp1[i][j]表示第i堆到第j堆最小得分,dp2[i][j]表示第i堆到第j堆最大得分,环状数组开两倍,状态转移方程如下:

dp1[i][j] = min(dp1[i][j],dp1[i][k] + dp1[k+1][j] + summ);
dp2[i][j] = max(dp2[i][j],dp2[i][k] + dp2[k+1][j] + summ);

PS:求最大值时不能采用四边形法则()。

AC_Code:

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long
const int maxn = 205;
const int inf = 0x3f3f3f3f;
int sum[maxn];
int dp1[maxn][maxn];//xiao
int dp2[maxn][maxn];//da
int weight[maxn];
int n;
#define ms1(s) memset(s,0,sizeof(s))
#define ms2(s) memset(s,inf,sizeof(s))

void solve(){
	for(int len = 2	; len <= 2*n; ++len){//changdu
		for(int i=1,j=len; j<=2*n; i++,j++){
			for(int k=i; k<j; ++k){
				//if(dp1[i][j]) dp1[i][j] = min(dp1[i][j],dp1[i][k] + dp1[k+1][j] + sum[j] - sum[i-1]);
				//else dp1[i][j] = dp1[i][k] + dp1[k+1][j] + sum[j] - sum[i-1];
				int summ;
				if(i <= n + 1 && j > n) summ = sum[j] + sum[n] - sum[i-1];
				else summ = sum[j] - sum[i-1];
				dp1[i][j] = min(dp1[i][j],dp1[i][k] + dp1[k+1][j] + summ);
				dp2[i][j] = max(dp2[i][j],dp2[i][k] + dp2[k+1][j] + summ);
			}
		}
	}
	int minn = inf;
	int maxx = -inf;
	for(int i=1; i<=n; ++i){
		minn = min(minn,dp1[i][i+n-1]);
		maxx = max(maxx,dp2[i][i+n-1]);
	}
	cout << minn << endl << maxx << endl;
}

int main(){
	cin >> n;
	ms2(dp1);ms1(dp2);
	for(int i=0; i<=2*n; ++i) dp1[i][i] = dp2[i][i] = 0;
	for(int i=1; i<=n; ++i) {
		cin >> weight[i];
		//dp1[i][i] = dp2[i][i] = weight[i];
	}
	for(int i=n+1; i<=2*n; ++i) weight[i] = weight[i-n];
	for(int i=1; i<=n; ++i) sum[i] = sum[i-1] + weight[i];
	for(int i=n+1; i<=2*n; ++i) sum[i] = sum[i-n];
	solve();
	return 0;
}

you are the one

题意:
有n个人上台演出,每个人ai有一个diaosi值di,他们的不开心值si会随着自己的出场顺序u而变化, si = di * (u - 1),求最小不开心值总和。

题解:
给的顺序是入栈的顺序。
区间dp : dp[i][j]表示第i个人到第j个人的最小不开心总值,sum[i]表示前i个数的和。

k表示i ~ j 区间第 i 个人(即 i 到 j 区间第一个人)第 k 个上台表演,而 i + 1 到 i + k - 1 区间的人是在 a[i] 前面上台的,就划分出了一个子问题 dp[i + 1][i + k - 1],因为i + 1 到 i + k - 1 区间的人是先上台的,dp[i + 1][i + k - 1] 就是他们的愤怒总量;而 i + k 到 j 区间的人就是在 a[i] 后上台的,有划分出了一个子问题,dp[i + k][j],但是由于该区间是在第 k 个后上台的,所以要加上他们的愤怒总量乘 k ,即 ((sum[j] - sum[i + k - 1]) * k);最后加上第 i 个人第 k 次上场的愤怒总量a[i] * (k -1),就是状态转移方程。

//
//Write by Yuan Xilan on 2019...
//
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf = 0x3f3f3f3f;
const int maxn = 222;
int dp[maxn][maxn];
int sum[maxn];
int a[maxn];
int n,z=0;
#define ms1(s) memset(s, 0, sizeof(s))
#define ms2(s) memset(s,inf,sizeof(s))
#define test(t) int t; cin >> t; while(t--)

void init(){
	ms2(dp);ms1(sum);
	cin >> n;
	for(int i=1; i<=n; ++i){
		cin >> a[i];
		sum[i] = sum[i-1] + a[i];
		dp[i][i] = 0;
	}
}

void solve(){
	for(int len = 1; len <= n; ++len){//长度 
		for(int i = 1, j = len; j <= n; ++i, ++j){//i表示起点,j表示终点 
			for(int k = 1; k <= len; ++k){//k表示a[i]插入的位置 
				if(k == 1) dp[i][j] = min(dp[i][j], dp[i+k][j] + (sum[j] - sum[i+k-1]) * k);
				else if(k == len) dp[i][j] = min(dp[i][j], dp[i+1][i+k-1] + a[i] * (k - 1));
				else dp[i][j] = min(dp[i][j], dp[i+1][i+k-1] + dp[i+k][j] + (sum[j] - sum[i+k-1]) * k + a[i] * (k - 1));
			}
		}
	}
	printf("Case #%d: %d\n", ++z, dp[1][n]);
}

int main(){
	test(t){
		init();
		solve();
	}
	return 0;
}

String painter

题意:
给定两个等长度的字符串,有一种刷新字符串的方法,它能够将一段字符串刷成同一个字符。现在要你使用这种方法,使得第一个字符串被刷成第二个字符串,问你最少需要刷多少次?

题解:
dp[i][j]维护区间i到j s1修改成s2的最少次数,考虑直接将一个空串刷成第二个字符串,然后再与第一个字符串去比较。这样,如果每个字符都是单独刷新,则dp[i][j] = dp[i+1][j]+1,如果在区间[i+1,j]之间有字符与t[i]相同,则可以将区间分为两个区间,分别为[i+1,k]和[k+1,j],考虑一起刷新.

AC_Code:

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long
const int maxn = 105;
char s1[maxn],s2[maxn];
int dp[maxn][maxn];

void solve(){
	int len = strlen(s1+1);
		for(int j=1; j<=len; ++j){
			for(int i=j; i>0; --i){
				dp[i][j] = dp[i+1][j] + 1;
				for(int k=i+1; k<=j; ++k){
					if(s2[k] == s2[i]){
						dp[i][j] = min(dp[i][j],dp[i+1][k] + dp[k+1][j]);	
					}
				}
			}
		}
		for(int i=1; i<=len; ++i){
			if(s1[i] == s2[i]){
				if(i != 1){
					dp[1][i] = dp[1][i-1];
				}
				else{
					dp[1][i] = 0;
				}
			}
			else{
				for(int j=1; j<i; ++j){
					dp[1][i] = min(dp[1][i],dp[1][j] + dp[j+1][i]);
				}
			}
		}
		printf("%d\n",dp[1][len]);
}

int main(){
	//freopen("in.txt","r",stdin);
	while(~scanf("%s%s",s1+1,s2+1)){
		memset(dp,0,sizeof(dp));
		solve();
	}
	return 0;
}		

Dire Wolf

题意:
你是一个战士现在面对,一群狼,每只狼都有一定的主动攻击力和附带攻击力。你杀死一只狼。你会受到这只狼的(主动攻击力+旁边两只狼的附带攻击力)这么多伤害~现在问你如何选择杀狼的顺序使的杀完所有狼时,自己受到的伤害最小。(提醒,狼杀死后就消失,身边原本相隔的两只狼会变成相邻,而且不需要考虑狼围城环这种情况)

题解:
dp[i][j]表示把第i只狼到第j只狼杀掉后的最小承伤量,设k表示在[i,j]范围内第k只狼是最后一个死的,那么该问题可分解为两个子问题,即dp[i][k-1]和dp[k+1][j],最后杀第k只狼时,除了自己的主动攻击力外,还会附带第i-1和j+1只狼的附带攻击力,因此有状态转移方程:

dp[i][j] = min(dp[i][j],dp[i][k-1]+dp[k+1][j]+a[k]+b[i-1]+b[j+1]);

PS:考虑i比k-1大和k-1比j大的情况。

AC_Code:

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long
#define inf 0x3f3f3f3f
const int maxn = 205;
int a[maxn],b[maxn];
int dp[maxn][maxn];
int times;

void solve(){
	int n;
	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]);
	for(int i=1; i<=n; ++i) dp[i][i] = a[i] + b[i-1] + b[i+1];
	for(int j=1; j<=n; ++j){//终点 
		for(int i=j; i>=1; --i){//起点 
			for(int k=i; k<=j; ++k){//第k只狼最后被杀死 
				if(k == i) dp[i][j] = min(dp[i][j],dp[i+1][j]+a[i]+b[i-1]+b[j+1]);
				else if(k == j) dp[i][j] = min(dp[i][j],dp[i][j-1]+a[j]+b[i-1]+b[j+1]);
				else dp[i][j] = min(dp[i][j],dp[i][k-1]+dp[k+1][j]+a[k]+b[i-1]+b[j+1]);
			}
		}
	}
	printf("Case #%d: %d\n",times,dp[1][n]);
}

int main(){
	//freopen("in.txt","r",stdin);
	int t;
	while(~scanf("%d",&t)){
		times = 0;
		while(t--){
			times += 1;
			memset(dp,inf,sizeof(dp));
			memset(a,0,sizeof(a));
			memset(b,0,sizeof(b));
			solve();
		}
	}
	return 0;
}		

猜你喜欢

转载自blog.csdn.net/qq_43298454/article/details/97516155
今日推荐