DP else

目录

BZOJ 3233 找硬币(贪心+动态规划)

CSU 2045 总和的最小值

HDU 1246 自共轭Ferrers图

CodeForces 706C Hard problem

UVA 580 Critical Mass

力扣 39. 组合总和

力扣 40. 组合总和 II

力扣 216. 组合总和 III

力扣 1553. 吃掉 N 个橘子的最少天数


BZOJ 3233 找硬币(贪心+动态规划)

题目:

Description

小蛇是金融部部长。最近她决定制造一系列新的货币。假设她要制造的货币的面值为x1,x2,x3… 那么x1必须为1,xb必须为xa的正整数倍(b>a)。例如1,5,125,250就是一组合法的硬币序列,而1,5,100,125就不是。不知从哪一天开始,可爱的蛇爱上了一种萌物——兔纸!从此,小蛇便走上了遇上兔纸娃娃就买的不归路。某天,小蛇看到了N只可爱的兔纸,假设这N 只兔纸的价钱分别是a1,a2…aN。现在小蛇想知道,在哪一组合法的硬币序列下,买这N只兔纸所需要的硬币数最少。买兔纸时不能找零,1<=N<=50, 1<=ai<=100,000

Input

第一行,一个整数N,表示兔纸的个数

第二行,N个用空格隔开的整数,分别为N只兔纸的价钱

Output

一行,一个整数,表示最少付的钱币数。

Sample Input

2 
25 102 

Sample Output

4 

Hint

样例解释:共有两只兔纸,价钱分别为25和102。现在小蛇构造1,25,100这样一组硬币序列,那么付第一只兔纸只需要一个面值为25的硬币,第二只兔纸需要一个面值为100的硬币和两个面值为1的硬币,总共两只兔纸需要付4个硬币。这也是所有方案中最少所需要付的硬币数。

这是一个贪心+动态规划问题

关于贪心比较好理解,在给定硬币序列的情况下,尽量使最大的硬币使用最多,然后再尽量使第二大的硬币使用最多,等等。。。

这种贪心策略,只在本题条件下成立,对于一般的硬币序列就不一定了。

动态规划就比较有趣了。

用dp[i]表示在最大的硬币为i的情况下,最少需要付的硬币数

那么dp[j]是满足i|j的所有i中,f(i,j)的最小值

其中,f(i,j)表示的是最大硬币为j,第二大的硬币为i时,最少需要付的硬币数,可以根据dp[i]直接求出来。

这样就得到了递推式,可以动态规划了,不过超时了。

于是上面的算法还可以优化:

dp[j]是满足i|j的所有i中,f(i,j)的最小值,这一点无疑是对的,但是还不够简单。

实际上,dp[j]是满足j=i*p(p为任意素数)的所有i中,f(i,j)的最小值

这样,计算可以变快,不过需要预先给出素数表。

我用的方法是筛法打表,把素数按顺序存到pri数组中

代码:

#include <iostream>
using namespace std;

int num[50], dp[100001], prime[100001], pri[10000];

int main()
{
	int n, ans, M = 0, result = 12345678, keyp = 0;
	cin >> n;
	dp[1] = 0;
	for (int i = 0; i < n; i++)
	{
		cin >> num[i];
		dp[1] += num[i];
		if (M < num[i])M = num[i];
	}
	for (int i = 1; i <= M; i++)prime[i] = 1;
	for (int i = 2; i <= M; i++)if (prime[i])
	{
		pri[keyp++] = i;
		for (int j = i; j <= M; j += i)prime[j] = 0;
	}
	for (int i = 2; i <= M; i++)dp[i] = 12345678;
	for (int i = 1; i <= M; i++)
	{
		if (dp[i] < result)result = dp[i];
		for (int k = 0; k < keyp; k++)
		{
			int j = i*pri[k];
			if (j>M)break;
			ans = dp[i];
		for (int k = 0; k < n; k++)ans -= num[k] / i - num[k] / j - num[k] % j / i;
			if (dp[j]>ans)dp[j] = ans;
		}
	}
	cout << result << endl;
	return 0;
}

CSU 2045 总和的最小值

 题目:

Description

给出一个长度为n的序列a和一个数字c 对于一个长度为k的序列b,它的价值是序列b中除了k/c(整数除法)个最小数字外其他的数字的和。例如,对于序列[3,1,6,5,2],c=2时的价值是3+6+5=14 问如何将a进行划分,得到的所有连续的子序列的价值之和最小。 请输出最小值。

Input

第一行2个整数n, c 第二行n个整数ai,表示序列a  1<= n, c<= 100 000, 1 < =ai < =109

Output

所有连续的子序列的价值之和的最小值。

Sample Input

12 10
1 1 10 10 10 10 10 10 9 10 10 10
8 4
1 3 4 5 5 3 4 1

Sample Output

92
23

思路:

首先是贪心思想,每个连续的子序列的长度都不超过c

实际上就是划分成若干段,每一段的长度不超过c,

其次是动态规划,用ans[i]表示前i个数对应的答案,

对于第i个数所在区间段的长度分类,如果长度小于c,那么ans[i]=ans[i-1]+a[i]

如果长度等于c,那么ans[i]=ans[i-c]+最后一段区间的价值,

对于一段长为c的区间的价值,只需要用总和减去其中的最小数即可,利用线段树可以在很短的时间内解决。

代码:

#include<iostream>
#include<stdio.h>
#include<algorithm>
using namespace std;

long long a[100005],ans[100005],minn[400005],sum[100005];

void build(int key,int low,int high)
{
	if(low==high)
	{
		minn[key]=a[low];
		return;
	}
	int mid=(low+high)/2;
	build(key*2,low,mid);
	build(key*2+1,mid+1,high);
	minn[key]=min(minn[key*2],minn[key*2+1]);
	
}

int query(int key,int low,int high,int x,int y)
{
	if(low==x&&high==y)return minn[key];
	int mid=(low+high)/2;
	if(mid<x)return query(key*2+1,mid+1,high,x,y);
	if(mid>=y)return query(key*2,low,mid,x,y);
	int a=query(key*2,low,mid,x,mid);
	int b=query(key*2+1,mid+1,high,mid+1,y);
	return min(a,b);
}

int main()
{
	int n,c;
	while(scanf("%d%d",&n,&c)!=EOF)
	{
		for(int i=1;i<=n;i++)scanf("%d",&a[i]);
		build(1,1,n);
		ans[0]=0,sum[0]=0;
		for(int i=1;i<c;i++)ans[i]=ans[i-1]+a[i];
		for(int i=1;i<=n;i++)sum[i]=sum[i-1]+a[i];
		for(int i=c;i<=n;i++)ans[i]=min(ans[i-1]+a[i],ans[i-c]+sum[i]-sum[i-c]-query(1,1,n,i-c+1,i));
		printf("%d\n",ans[n]);	
	}
	return 0;
}

HDU 1246 自共轭Ferrers图

题目:

Description

Ferrers图是一个自上而下的n层格子,且上层格子数不少于下层格子数。 
 
如上图所示,图中的虚线称为Ferrers图的虚轴。若将图一绕虚轴旋转180°,即将第一行与第一列对调,将第二行与第二列对调,……,这样所得到的图仍为Ferrers图,如下图所示。 
 
这两个图称为一对共轭Ferrers图。有一些Ferrers图,沿虚轴转换后的Ferrers图仍为它本身,也就是说这个Ferrers图关于虚轴对称,那么这个Ferrers图称为自共轭Ferrers图。下图便是一个自共轭Ferrers图。 
 
现在我们的目标是寻找的是大小为n的自共轭Ferrers图的总数。所谓大小为n的自共轭Ferrers图是指由n个方格组成的自共轭Ferrers图。 

Input

输入数据有多行,每行为一个正整数n(1<=n<=300)。表示自共轭Ferrers图的大小为n。 

Output

对应输入的每一个n,输出一行大小为n的自共轭Ferrers图的总数。 

Sample Input

1
2
3

Sample Output

1
0
1

定理:对于一个大小为n(n>1)的自共轭Ferrers图,去掉第一行和第一列,得到的还是自共轭Ferrers图,而且它的第一行一定比原图的第一行要短,假设得到的自共轭Ferrers图的大小为n-k那么k一定是奇数。
这个结论是非常显然的。
这样,不断地如此剖分,一个自共轭Ferrers图可以表示成1个由上述k组成的数列。

例如:

这个图可以表示成13,9,7,1

假设一个自共轭Ferrers图,大小为n,第一行和第一列一共有k个格子,那么这样的图的总数为f(n,k)

可以得到递推式:f(n,k)=f(n,k-2)+f(n-k,k-2)

由此即可动态规划求解。

HDU - 1244 Max Sum Plus Plus Plus  一样,在普通的动态规划之上,还用了空间压缩,就不需要二维数组。

代码:

#include<iostream>
using namespace std;

int main()
{
	int list[301];
	for (int i = 0; i < 301; i++)list[i] = (i < 2);
	for (int k = 3; k < 301; k += 2)for (int n = 300; n >= k; n--)list[n] += list[n - k];
	int n;
	while (cin >> n)cout << list[n] << endl;
	return 0;
}

0ms AC

CodeForces 706C Hard problem

题目:

Description

Vasiliy is fond of solving different tasks. Today he found one he wasn't able to solve himself, so he asks you to help.

Vasiliy is given n strings consisting of lowercase English letters. He wants them to be sorted in lexicographical order (as in the dictionary), but he is not allowed to swap any of them. The only operation he is allowed to do is to reverse any of them (first character becomes last, second becomes one before last and so on).

To reverse the i-th string Vasiliy has to spent ci units of energy. He is interested in the minimum amount of energy he has to spent in order to have strings sorted in lexicographical order.

String A is lexicographically smaller than string B if it is shorter than B (|A| < |B|) and is its prefix, or if none of them is a prefix of the other and at the first position where they differ character in A is smaller than the character in B.

For the purpose of this problem, two equal strings nearby do not break the condition of sequence being sorted lexicographically.

Input

The first line of the input contains a single integer n (2 ≤ n ≤ 100 000) — the number of strings.

The second line contains n integers ci (0 ≤ ci ≤ 109), the i-th of them is equal to the amount of energy Vasiliy has to spent in order to reverse the i-th string.

Then follow n lines, each containing a string consisting of lowercase English letters. The total length of these strings doesn't exceed100 000.

Output

If it is impossible to reverse some of the strings such that they will be located in lexicographical order, print  - 1. Otherwise, print the minimum total amount of energy Vasiliy has to spent.

Sample Input

Input

2
1 2
ba
ac

Output

1

Input

3
1 3 1
aa
ba
ac

Output

1

Input

2
5 5
bbb
aaa

Output

-1

Input

2
3 3
aaa
aa

Output

-1

代码:

#include<iostream>
#include<string>
#include<cstring>
using namespace std;

int c[100000];

bool ok(string s1,int k1, string s2,int k2)
{
	string s3;
	int l1 = s1.length(), l2 = s2.length();
	if (k1)
	{
		s3 = s1;
		for (int i = 0; i < l1; i++)s1[i] = s3[l1 - i - 1];
	}
	if (k2)
	{
		s3 = s2;
		for (int i = 0; i < l2; i++)s2[i] = s3[l2 - i - 1];
	}
	int i = 0;
	while (i < l1 && i < l2)
	{
		if (s1[i] < s2[i])return true;
		if (s1[i] > s2[i])return false;
		i++;
	}
	if (l1 <= l2)return true;
	return false;
}

int main()
{
	int n;
	long long sum1 = 0, sum2 = 0, t1, t2;
	scanf("%d", &n);
	for (int i = 0; i < n; i++)scanf("%d", &c[i]);
	string s1="", s2;
	for (int i = 0; i < n; i++)
	{
		cin >> s2;
		t1 = -1, t2 = -1;
		if (sum1 >= 0)
		{
			if (ok(&s1[0], 0, &s2[0], 0))t1 = sum1;
			if (ok(&s1[0], 0, &s2[0], 1))t2 = sum1 + c[i];
		}
		if (sum2 >= 0)
		{
			if (ok(&s1[0], 1, &s2[0], 0))
				if (t1<0 || t1>sum2)t1 = sum2;
			if (ok(&s1[0], 1, &s2[0], 1))
				if (t2<0 || t2>sum2 + c[i])t2 = sum2 + c[i];
		}
		sum1 = t1;
		sum2 = t2;
		s1 = s2;
	}
	if (sum1 < 0 && sum2 < 0)cout << -1;
	else if (sum1 < 0)cout << sum2;
	else if (sum2 < 0)cout << sum1;
	else cout << ((sum1 < sum2) ? sum1 : sum2);
	cout << endl;
	return 0;
}

用了空间压缩,把一维数组压成了sum1、sum2、t1、t2这4个变量。

所以看起来动态规划的特征不是很明显,不过仔细想想,本质上还是动态规划。

UVA 580 Critical Mass

 题目大意就是,对于输入的n,n<=30,求有多少个不超过n位2进制的数,有连续的3个1。

f(3)=1,111

f(4)=3,0111,1110,1111

f(5)=8,11100,11101,11110,11111,01110,01111,10111,00111

等等。。。

题目:

可以求出递推公式:list[i] = list[i - 1] * 2+ pow(2, i - 4) -list[i-4]

可以把右边理解为2部分。

第一部分,不管最左边的是L还是U,只要右边n-1个里面有3个连续的U,那么这n个也是的了,所以是 list[i - 1] * 2

第二部分,右边n-1个里面没有3个连续的U,但是最左边加了一个字符之后,就有了。

那么,这n个字符必须以UUUL开头,而且剩下的n-4个是不能有3个连续的U的,所以是pow(2, i - 4) -list[i-4]

代码1:

#include<iostream>
using namespace std;

int main()
{
	int list[31];
	list[0] = 0;
	list[1] = 0;
	list[2] = 0;
	list[3] = 1;
	list[4] = 3;
	for (int i = 5; i < 31; i++)list[i] = list[i - 1] * 2 -list[i-4]+ pow(2, i - 4);
	for (int i = 0; i < 31; i++)cout << list[i] << ",";
	system("pause>nul");
	return 0;
}

因为我是win10,所以爽到爆的一点就是,dos是可以复制也可以粘贴的。

于是我把代码1的输出复制粘贴到代码2

代码2:

#include<iostream>
using namespace std;

int main()
{
	int list[31] = { 0, 0, 0, 1, 3, 8, 20, 47, 107, 238, 520, 1121, 2391, 5056, 10616, 22159, 46023, 95182, 196132, 402873, 825259, 1686408, 3438828, 6999071, 14221459, 28853662, 58462800, 118315137, 239186031, 483072832, 974791728 };
	int n;
	while (cin >> n)
	{
		if (n == 0)break;
		cout << list[n] << endl;
	}	
	return 0;
}

力扣 39. 组合总和

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。 

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

示例 1:

输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。

示例 2:

输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]

示例 3:

输入: candidates = [2], target = 1
输出: []

提示:

  • 1 <= candidates.length <= 30
  • 2 <= candidates[i] <= 40
  • candidates 的所有元素 互不相同
  • 1 <= target <= 40
class Solution {
public:
	vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
		return combinationSum(candidates, target, 0);
	}
	vector<vector<int>> combinationSum(vector<int>& candidates, int target, int id) {
		if (target == 0)
			return vector<vector<int>>{1};
		if (target < 0 || id>= candidates.size())return vector<vector<int>>{};
		vector<vector<int>>ans = combinationSum(candidates, target - candidates[id], id);
		for (auto& vi : ans)vi.insert(vi.begin(), candidates[id]);
		vector<vector<int>>ans2 = combinationSum(candidates, target, id + 1);
		return FvecJoin<vector<int>>(ans, ans2);
	}
};

力扣 40. 组合总和 II

给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用 一次 。

注意:解集不能包含重复的组合。 

示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]

示例 2:

输入: candidates = [2,5,2,1,2], target = 5,
输出:
[
[1,2,2],
[5]
]

提示:

  • 1 <= candidates.length <= 100
  • 1 <= candidates[i] <= 50
  • 1 <= target <= 30
class Solution {
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<vector<int>> res;
        vector<int> path;
        sort(candidates.begin(), candidates.end()); // 对候选人编号集合进行排序
        backtrack(candidates, target, 0, path, res);
        return res;
    }

    void backtrack(vector<int>& candidates, int target, int start, vector<int>& path, vector<vector<int>>& res) {
        if (target == 0) { // 如果当前的数字和等于目标数,就将当前的组合加入结果集
            res.push_back(path);
            return;
        }
        for (int i = start; i < candidates.size() && candidates[i] <= target; i++) {
            if (i > start && candidates[i] == candidates[i-1]) continue; // 去重
            path.push_back(candidates[i]);
            backtrack(candidates, target-candidates[i], i+1, path, res); // 从当前数字的下一个位置开始继续回溯
            path.pop_back();
        }
    }
};

力扣 216. 组合总和 III

找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:

  • 只使用数字1到9
  • 每个数字 最多使用一次 

返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

示例 1:

输入: k = 3, n = 7
输出: [[1,2,4]]
解释:
1 + 2 + 4 = 7
没有其他符合的组合了。

示例 2:

输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
解释:
1 + 2 + 6 = 9
1 + 3 + 5 = 9
2 + 3 + 4 = 9
没有其他符合的组合了。

示例 3:

输入: k = 4, n = 1
输出: []
解释: 不存在有效的组合。
在[1,9]范围内使用4个不同的数字,我们可以得到的最小和是1+2+3+4 = 10,因为10 > 1,没有有效的组合。

提示:

  • 2 <= k <= 9
  • 1 <= n <= 60
class Solution {
public:
    vector<vector<int>> combinationSum3(int k, int n) {
        return combinationSum3(k,n,9);
    }
    vector<vector<int>> combinationSum3(int k, int n, int m) {
        vector<vector<int>> ans;
        if(k==1){
            if(n>=1 && n<=m)return vector<vector<int>>{vector<int>{n}};
            return ans;
        }
        if(m>k) ans= combinationSum3(k,n,m-1);
        auto v = combinationSum3(k-1,n-m,m-1);
        for(auto &vi:v){
            vi.push_back(m);
            ans.push_back(vi);
        }
        return ans;
    }
};

力扣 1553. 吃掉 N 个橘子的最少天数

厨房里总共有 n 个橘子,你决定每一天选择如下方式之一吃这些橘子:

  • 吃掉一个橘子。
  • 如果剩余橘子数 n 能被 2 整除,那么你可以吃掉 n/2 个橘子。
  • 如果剩余橘子数 n 能被 3 整除,那么你可以吃掉 2*(n/3) 个橘子。

每天你只能从以上 3 种方案中选择一种方案。

请你返回吃掉所有 n 个橘子的最少天数。

示例 1:

输入:n = 10
输出:4
解释:你总共有 10 个橘子。
第 1 天:吃 1 个橘子,剩余橘子数 10 - 1 = 9。
第 2 天:吃 6 个橘子,剩余橘子数 9 - 2*(9/3) = 9 - 6 = 3。(9 可以被 3 整除)
第 3 天:吃 2 个橘子,剩余橘子数 3 - 2*(3/3) = 3 - 2 = 1。
第 4 天:吃掉最后 1 个橘子,剩余橘子数 1 - 1 = 0。
你需要至少 4 天吃掉 10 个橘子。

示例 2:

输入:n = 6
输出:3
解释:你总共有 6 个橘子。
第 1 天:吃 3 个橘子,剩余橘子数 6 - 6/2 = 6 - 3 = 3。(6 可以被 2 整除)
第 2 天:吃 2 个橘子,剩余橘子数 3 - 2*(3/3) = 3 - 2 = 1。(3 可以被 3 整除)
第 3 天:吃掉剩余 1 个橘子,剩余橘子数 1 - 1 = 0。
你至少需要 3 天吃掉 6 个橘子。

示例 3:

输入:n = 1
输出:1

示例 4:

输入:n = 56
输出:6

提示:

  • 1 <= n <= 2*10^9
class Solution {
public:
    int minDays(int n) {
        return dp(n);
    }
    int dp(int n){
        if(m[n])return m[n];
        if(n<3)return n;
        return m[n]=min(dp(n/2)+n%2,dp(n/3)+n%3)+1;
    }
    unordered_map<int,int>m;
};

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值