zoukankan      html  css  js  c++  java
  • 线性DP两题

    好雨知时节,当春乃发生。
    随风潜入夜,润物细无声。
    野径云俱黑,江船火独明。
    晓看红湿处,花重锦官城。——杜甫

    题目一:数的划分

    网址:https://www.luogu.com.cn/problem/P1025

    题目描述

    将整数n分成k份,且每份不能为空,任意两个方案不相同(不考虑顺序)。

    例如:n=7,k=3,下面三种分法被认为是相同的。

    1, 1, 5;
    1, 5, 1;
    5, 1, 1.

    问有多少种不同的分法。

    输入格式

    n, k(6<n≤200,2≤k≤6)

    输出格式

    1个整数,即不同的分法。

    输入输出样例
    输入
    7 3
    
    输出
    4
    
    说明/提示

    四种分法为:
    1, 1, 5;
    1, 2, 4;
    1, 3, 3;
    2, 2, 3.

    方法一:搜索

    搜索框架不难,可以加记忆化,可以优化和剪枝。

    代码如下:

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    using namespace std;
    const int maxn = 200 + 5, maxk = 7;
    int n, k, d[maxn][maxn][maxk];
    int dfs(int p, int cur, int dep)
    {
    	if(p < 0 || dep < 0) return 0;//不符题意
    	int &ans = d[p][cur][dep];
    	if(cur * dep > p || cur > n) return ans = 0;//剪枝操作
    	
    	if(ans != -1) return ans;//记忆化
    	if(!dep)
    	{
    		if(!p) return ans = 1;
    		return ans = 0;
    	}
    	return ans = dfs(p, cur + 1, dep) + dfs(p - cur, cur, dep - 1);//两种选择 
    }
    int main()
    {
    	memset(d, -1, sizeof(d));//初始化,防止重复遍历同一个结点
    	scanf("%d %d", &n, &k);
    	printf("%d
    ", dfs(n, 1, k));
    	return 0;
    }
    
    方法二:动态规划

    不难想到令dp[i, j]表示剩余数字为i,划分j段序列之方案总和。显然有:dp[i, j] = sigma{dp[i - l, j - 1] | 0 < l <= i}。

    留意到1, 2, 3和2, 3, 1以及2, 1, 3算一种相同的方案(组合),刚刚的转移方程似乎是无法避免这点的。通过调整顺序,我们可以将转移方程的l移到最外层,从而保证没有重复地计算。

    代码如下:

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    using namespace std;
    const int maxn = 200 + 5, maxk = 10 + 5;
    int n, k, dp[maxn][maxk] = {};
    int main()
    {
    	scanf("%d %d", &n, &k);
    	dp[0][0] = 1;
    	for(int l = 1; l <= n; ++ l)
    	{
    		for(int i = l; i <= n; ++ i)
    		{
    			for(int j = 1; j <= k; ++ j)
    			{
    				dp[i][j] += dp[i - l][j - 1];
    			}
    		}
    	}
    	printf("%d
    ", dp[n][k]);
    	return 0;
    }
    

    其实可以更好。

    对于该状态,统计个数时我们分两类讨论:划分的数中至少有一个为1和没有一个为1的方案数总和(显然是可以包含所有情况的)。
    当至少有一个为1时方案数为:dp[i - 1, j - 1],因为既然是确定了划分的一个数是1,那么把它减去不影响结果(方案中等价于没有它)。
    当没有一个数为1时,显然方案数等于每一个划分出来的数-1(形象地认为:给定i个球装进j个盒子,要求:每个盒子不能为空,那么如果每个盒子都装有多个球,那么从任意一个盒子中取出1个球不影响结果)。
    则必有:dp[i, j] = dp[i - 1, j - 1] + dp[i - j, j],

    代码如下:

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    using namespace std;
    const int maxn = 200 + 10;
    int n, k, dp[maxn][7];
    int main()
    {
    	scanf("%d %d", &n, &k);
    	memset(dp, 0, sizeof(dp));
    	for(int i = 1; i <= k; ++ i) dp[i][i] = 1;
    	for(int i = 1; i <= n; ++ i)
    	{
    		for(int j = 1; j <= min(i - 1, k); ++ j)
    		{
    			dp[i][j] = dp[i - 1][j - 1] + dp[i - j][j];
    		}
    	}
    	printf("%d
    ", dp[n][k]);
    	return 0;
    }
    
    题目二:放苹果

    网址:http://poj.org/problem?id=1664

    题目描述

    把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分发(5,1,1和1,1,5是同一种方法)

    输入格式

    第一行是测试数据的数目t(0 <= t <= 20),以下每行均包括二个整数M和N,以空格分开。1<=M,N<=10

    输出格式

    对输入的每组数据M和N,用一行输出相应的K。

    输入输出样例
    输入
    1
    7 3
    
    输出
    8
    
    输入
    3
    3 2
    4 3
    2 7
    
    输出
    2
    4
    2
    

    看到了这道题的区别:允许空集出现了。

    但是,n多枚举0。问题易解了!

    代码如下:

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #define SIZE 10 + 5
    using namespace std;
    int m, n, dp[SIZE][SIZE];
    int main()
    {
    	int T;
    	scanf("%d", &T);
    	while(T --)
    	{
    		scanf("%d %d", &m, &n);
    		memset(dp, 0, sizeof(dp));
    		dp[0][0] = 1;
    		for(int k = 0; k <= m; ++ k)
    		{
    			for(int i = k; i <= m; ++ i)
    			{
    				for(int j = 1; j <= n; ++ j)
    				{
    					dp[i][j] += dp[i - k][j - 1];
    				}
    			}
    		}
    		printf("%d
    ", dp[m][n]);
    	}
    	return 0;
    }
    

    其实可以仿照之前的做法。

    有dp[i, j] = dp[i, j - 1] + dp[i - j, j]。留意j的范围是[1, n],因此,当i < j时,由抽屉原理可得:必有盘子是空的,因此,dp[i, j] = dp[i, i]。

    代码如下:

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    using namespace std;
    const int maxn = 10 + 5;
    int m, n, dp[maxn][maxn];
    int main()
    {
    	int T;
    	scanf("%d", &T);
    	while(T --)
    	{
    		scanf("%d %d", &m, &n);
    		memset(dp, 0, sizeof(dp));
    		dp[1][1] = 1;
    		for(int i = 1; i <= n; ++ i) dp[0][i] = 1;
    		for(int i = 1; i <= m; ++ i)
    		{
    			for(int j = 1; j <= n; ++ j)
    			{
    				if(i < j) dp[i][j] = dp[i][i];
    				else dp[i][j] = dp[i][j - 1] + dp[i - j][j];
    			}
    		}
    		printf("%d
    ", dp[m][n]);
    	}
    	return 0;
    }
    
  • 相关阅读:
    CTF SQL注入知识点
    Rot13加密算法
    LFU缓存
    Redability
    快排
    更新卡片的zIndex
    webshell文件下载器
    [转]背包九讲
    hihocoder第196周
    Python import容易犯的一个错误
  • 原文地址:https://www.cnblogs.com/zach20040914/p/12853262.html
Copyright © 2011-2022 走看看