简单区间dp(nyoj 746 && poj 2955)

32 篇文章 0 订阅
11 篇文章 0 订阅

下面是在别的地方看到的(虽然不知道准不准确先转再说……):

区间动态规划问题一般都是考虑,对于每段区间,他们的最优值都是由几段更小区间的最优值得到,是分治思想的一种应用,将一个区间问题不断划分为更小的区间直至一个元素组成的区间,枚举他们的组合 ,求合并后的最优值。

设F[i,j](1<=i<=j<=n)表示区间[i,j]内的数字相加的最小代价
最小区间F[i,i]=0(一个数字无法合并,∴代价为0)

每次用变量k(i<=k<=j-1)将区间分为[i,k]和[k+1,j]两段

For p:=1 to n do // p是区间长度,作为阶段。 
for i:=1 to n do // i是穷举的区间的起点
begin
j:=i+p-1; // j是 区间的终点,这样所有的区间就穷举完毕
if j>n then break; // 这个if很关键。
for k:= i to j-1 do // 状态转移,去推出 f[i,j]
f[i , j]= max{f[ i,k]+ f[k+1,j]+ w[i,j] } 
end; 
这个结构必须记好,这是区间动态规划的代码结构。

nyoj 746:点击打开链接

状态转移方程:dp[i][j] = max(dp[i][j], dp[k][j - 1] * num[k + 1][i]) 定义dp[i][j]为将下标到i为止的数字串分成j个部分后乘积的最大值。

k是起点和终点之间的一个分割点, dp[k][j - 1]是将前面下标到k为止的数字串分成j - 1个部分所得的乘积的最大值, 为什么是j  - 1呢,因为k就是一个分割点。num[k + 1][i]是k + 1位置到i位置的对应数的乘积。

通过枚举分割点的位置找到乘积的最大值。

#include <stdio.h>
#include <string.h>
#define MAX 10000000
int m;
char n[25];
long long num[25][25];
long long dp[25][25];

void change()
{//将每一段对应的数字存储到num数组中
	int i, j, len = strlen(n);
	int k = 0;
	for(i = 0; i < len; i++)
	{
		k = 0;
		for(j = i; j < len; j++)
		{
			k = k * 10 + (n[j] - 48);
			num[i][j] = k;
		}
	}
}

long long max(long long a, long long b)
{
	return a > b ? a : b;
}

int main (void)
{
	int t;	
	scanf("%d", &t);
	while(t --)
	{
		memset(dp, 0, sizeof(dp));
		scanf("%s %d", n, &m);
		change();
		int i, j, k, len = strlen(n);
		for(i = 0; i < len; i++)
		//将0-i位置的数分成1份所得的最大值就是这个数本身 
			dp[i][1] = num[0][i];
		
		for(i = 0; i < len; i++)
		{//当前划分的长度为i 
			for(j = 1; j <= m; j++)
			{//划分成j个部分 
				for(k = 0; k <= i; k++)//划分的位置 
					dp[i][j] = max(dp[i][j], dp[k][j - 1] * num[k + 1][i]);
			}
		}
		printf("%lld\n", dp[len - 1][m]);
	}
	return 0;
}


poj 2955:点击打开链接

dp[i][j]表示起点为i终点为j的匹配的括号数的最大值。

#include <stdio.h>
#include <string.h>
 
char s[110];
int dp[110][110];

int yes(char a, char b)
{
	if(a == '(' && b == ')')
		return 1;
	if(a == '[' && b == ']')
		return 1;
	return 0;
}

int max (int a, int b)
{
	return a > b ? a : b;
}

int main (void)
{
	while(scanf("%s", s) != EOF)
	{ 
		if(s[0] == 'e')
			break;
		int len = strlen(s);
		memset(dp, 0, sizeof(dp));
		int i, j, k, p;
		for(i = 0; i < len - 1; i++)
			if(yes(s[i], s[i + 1]))
				dp[i][i + 1] = 2;//如果 
				
		for(p = 3; p <= len; p++)
		{//p是字符串的长度,因为上面已经配对过相邻两个括号所以p从3开始 
			for(i = 0; i < len; i++)
			{//i是起点 
				j = i + p - 1;//j为终点 
				if(j >= len)
					break;
				if(yes(s[i], s[j]))//如果匹配 
					dp[i][j] = dp[i + 1][j - 1] + 2;
			//2是已经匹配的i位置和j位置的两个括号,再加上剩下的i+1位置到j-1位置字串的最大匹配数 
				for(k = i; k < j; k++)//k是分割点 
					dp[i][j] = max(dp[i][j], dp[i][k] + dp[k + 1][j]);
					//i位置到k位置的匹配最大值加上k+1位置到j位置匹配的最大值 
			}
		}
		printf("%d\n", dp[0][len - 1]);
	}
	return 0;
}


Problem 111: 单词的划分


Time Limit:1 Ms|  Memory Limit:64 MB
Difficulty:2

Description

有一个很长的由小写字母组成字符串。为了便于对这个字符串进行分析,需要将它划分成若干个部分,每个部分称为一个单词。出于减少分析量的目的,我们希望划分出的单词数越少越好。你就是来完成这一划分工作的

Input

第一行,一个字符串。(字符串的长度不超过100)
第二行一个整数n,表示单词的个数。(n<=100)
第3~n+2行,每行列出一个单词。

Output

一个整数,表示字符串可以被划分成的最少的单词数。

Sample Input

realityour
5
real
reality
it
your
our

Sample Output

2

Hint

原字符串可拆成real+it+your或reality+our,由于reality+our仅为两个部分,因此最优解为2,另外注意,单词列表中的每个单词都可以重复使用多次,也可以不用

这个也是个区间动归题。

dp[i]为长度是i的单词所划分的最小单词数。

#include <stdio.h>
#include <string.h>

char s[110];
int n;
char word[110][50];
int dp[110];

int same(int x, int y)
{//从后向前比较 
	int i, j, l = strlen(word[y]);
	for(i = 0; i < l; i++)
	{
		if(s[x - i] != word[y][l - 1 - i])
			return 0;
	}
	return 1;
}

void init()
{
	int i, j;
	for(i = 0; i < 110; i++)
		dp[i] = 10000000;
}

int min (int a, int b)
{
	return a < b ? a : b;	
}

int main (void)
{
	while(scanf("%s", &s[1]) != EOF)
	{
		scanf("%d", &n);
		int i, j;
		for(i = 0; i < n; i++)
			scanf("%s", word[i]);
		int len = strlen(&s[1]);
		init();
		dp[0] = 0;
		for(i = 1; i <= len; i++)
		{//i是长度。注意i要从1开始,不然下面dp[i - 1]会出错 
			for(j = 0; j < n; j++)
			{//j遍历所给的单词 
				int l = strlen(word[j]);
				if(i < l - 1)
					continue;
				if(same(i, j))
				{//如果能分成某个单词,就选择最小的 
					dp[i] = min(dp[i], dp[i - l] + 1);
				}
			}
		}
		/*
		for(i = 0; i <= len; i++)
			printf("%d  ", dp[i]);
		printf("\n");
		*/
		printf("%d\n", dp[len]);
	}
	return 0;
}


参考:点击打开链接


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值