划分型动规

划分型动规:
给定长度为N的序列或字符串,要求划分成若干段
—段数不限,或指定K段
—每一段满足一定的性质

给定一个正整数N
问最少可以将N分成几个完全平方数(1,4,9,…)之和

例:
输入:N = 13
输出:2(13 = 4 + 9)

确定状态:
最后一步:关注最优策略中最后一个完全平方数 j ^ 2
最优策略中 n-j^2 也一定被分成最少的几个完全平方数之和
得到子问题
状态:设f[i]表示i最少被分成几个完全平方数之和
f[i] = min(f[i - j ^ 2] + 1)

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;

int n, d[1000000];

int main() {
	scanf("%d", &n);
	memset(d, 0x3f, sizeof(d));
	d[0] = 0;
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j * j <= i; j++) {
			d[i] = min(d[i - j * j] + 1, d[i]);
		}
	}
	
	for(int i = 0; i <= n; i++) {
		printf("%d ", d[i]);
	}

	printf("%d", d[n]);
	return 0;
}

时间复杂度O(N * N^1.5)

给定一个字符串S[0…N-1]
要求将这个字符串划分成若干段,每一段都是一个回文串
求最少划分几次

例子:
输入:
aab
输出:
1(划分1次->aa, b)

回文串判断
回文串分两种:
长度为奇数
长度为偶数
假设我们现在不是寻找回文串,而是生成回文串
从中间开始,向两边扩展,每次左右两端加上同样的字符
对于n个字符的串,奇数回文串的对称轴有n个,偶数回文串的对称轴有n-1个,共有2n个对称轴,一个回文串长度最多为n/2则2n*n/2 = n^2

记录回文串
从S每一个字符开始向两边扩展
考虑奇数回文串和偶数回文串
用isPalin[i][j]表示S[i…j]是否是回文串
时间复杂度O(N^2)

状态方程:
d[i] = min{d[j] + 1| isPalin[j][i - 1] = True}
d[i]表示有几个回文串
答案是d[n] - 1

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;

int n, d[10000];
bool is[10000][10000];
char s[10000];

void isPalin() {
	memset(is, false, sizeof(is));
	
	//奇数长度
	for(int c = 0; c < n; c++) {
		int i = c;
		int j = c;
		while(i >= 0 && j < n && s[i] == s[j]) {
			is[i][j] = true;
			--i;
			++j;
		}
	} 
	
	//偶数长度
	for(int c = 0; c < n - 1; c++) {
		int i = c;
		int j = c + 1;
		while(i >= 0 && j < n && s[i] == s[j]) {
			is[i][j] = true;
			--i;
			++j;
		}
	} 
}

int main() {
	scanf("%s", s);
	n = strlen(s);
	isPalin();
	d[0] = 0;
	for(int i = 1; i <= n; i++) {
		d[i] = 0x3f3f3f3f;
		for(int j = 0; j < i; j++) {
			if(is[j][i - 1]) {
				d[i] = min(d[i], d[j] + 1);
			}
		}
	}
	
//	for(int i = 0; i <= n; i++) {
//		printf("%d ", d[i]);
//	}
	
	printf("%d", d[n] - 1);
	return 0;
}

Copy Books
有N本书需要被抄写,第i本书有A[i]页,i = 0,1,…,N-1
有K个抄写员,每个抄写员可以抄写连续的若干本书
每个抄写员的抄写速度都一样:一分钟一页
最少需要多少时间抄写完所有的书

输入:
A=3,2,4
K =2
输出:
5(第一个抄写员抄写第1本书和第2本书,第二个抄写员抄写第3本书)

如果一个抄写员抄写第i本到第j本书,则需要时间A[i]+A[i+1]+…+A[j]
最后完成时间取决于耗时最长的那个抄写员
需要找到一种分段方式,分成不超过K段,使得所有段的数字之和的最大值最小

设d[k][i]为前K个抄写员最少需要多少时间抄完前i本书
状态方程:
d[k][i] = min(j =0,…,i){max{d[k-1][j], a[j]+…+a[i - 1]}}

初始条件:
0个抄写员只能抄0本书
d[0][0] = 0, d[0][1] = d[0][2] = …=d[0][N] = 正无穷
k个抄写员(k>0)需要0时间抄0本书
d[k][0] = 0(k > 0)

如果K>N, 赋值K = N

时间复杂度O(KN^2)

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;

int d[1000][1000], a[1000], n, K;

void DP() {
	d[0][0] = 0;
	for(int i = 1; i <= n; i++) {
		d[0][i] = 0x4f4f4f4f;
	}
	
	for(int k = 1; k <= K; k++) {
		d[k][0] = 0;
		for(int i = 1; i <= n; i++) {
			d[k][i] = 0x4f4f4f4f;
			int sum = 0;
			for(int j = i; j >= 0; --j) {
				d[k][i] = min(d[k][i], max(d[k - 1][j], sum));
				if(j > 0) {
					sum += a[j - 1];
				}
			}
		}
	}
}

int main() {
	scanf("%d%d", &n, &K);
	for(int i = 0; i < n; i++) {
		scanf("%d", &a[i]);
	}
	if(K > n ) K = n;
	DP();
	printf("%d", d[K][n]); 
	return 0;
}

小结:
划分型动态规划
要求将一个序列或字符串划分成若干段满足要求的片段
解决方法:最后一步->最后一段
枚举最后一段的起点

如果题目不指定段数,用d[i]表示前i个元素分段后的可行性,最值,方式数:第1、2题
如果题目指定段数,用d[i][j]表示前i个元素分成j段后的可行性,最值,方式数:第3题

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值