kuangbin的DP习题 - I



前言

Series II开始

Sumsets - POJ - 2229

题意
求一个数字拆成2次幂所组成的数的组合种类
思路
假设这个数为i
如果i是奇数,那么dp[i] = dp[i - 1],因为只需要在i - 1的组合种类中加一个’1’,就是i的组合种类了
如果i是偶数,那么其中一部分可通过i - 2的组合种类中加上两个’1’得到,另一部分都是偶数,那就通过i/2的组合种类中的每个元素都乘上2得到
故状态转移方程为 dp[i] = dp[i - 1](i为奇数), dp[i] = dp[i/2] + dp[i - 2](i为偶数)

	#include<cstdio>
	#include<algorithm>
	using namespace std;
	const int N = 1e6 + 15;
	typedef long long ll;
	const int MOD = 1e9;

	int dp[N];

	int main(){
		dp[1] = 1, dp[2] = 2;
		for(int i = 3; i <= N; i ++){
			if(i&1)	 dp[i] = dp[i - 1];
			else     dp[i] = (dp[i - 2] + dp[i >> 1] >= MOD ? (dp[i - 2] + dp[i >> 1])%MOD : dp[i - 2] + dp[i >> 1]);
		}

		int n;
		while(~scanf("%d", &n)){
			printf("%d\n", dp[n]);
		}
	}


Max Sum Plus Plus - HDU - 1024

题意
选择一段序列中的m个不相交段使他们的和最大
思路
定义dp[i][j]为前i个元素并且选择第i个元素且分成j段时的最大值,则dp[i][j] = max(dp[i - 1][j], max(dp[1][j - 1], dp[2][j - 1], ..., dp[i - 1][j - 1]) + a[i]
当前选择第i个元素,则有两种情况:
第一种是把这个元素接在第 i - 1 个元素所在段的后面
第二种是自成一段,那么取dp[1][j - 1] ~ dp[i - 1][j - 1]中的最大值,这个值可以在前面的计算中得出,故使用滚动数组

	#include <cstdio>
	#include <algorithm>
	#include <cstring>
	using namespace std;
	const int N =   1e6 + 15;
	const int inf = 0x7fffffff;

	int a[N];
	int dp[N], pre_dp[N];

	int main(){
	    int n, m, ans;
	    while(~scanf("%d%d", &m, &n)){
	        memset(dp, 0, sizeof(dp));
	        memset(pre_dp, 0, sizeof(pre_dp));
	        for(int i = 1; i <= n; i++){
	            scanf("%d", &a[i]);
	        }
	        for(int j = 1; j <= m; j++){
	            ans = -inf;
	            for(int i = j; i <= n; i++){
	                dp[i] = max(dp[i - 1] + a[i], pre_dp[i - 1] + a[i]);
	                pre_dp[i - 1] = ans;
	                ans = max(dp[i], ans);
	            }
	        }
	        printf("%d\n", ans);
	    }
	}


Tickets - HDU - 1260

题意
排队买票时前面有n个人,每个人可买单人票,或者相邻的人可买双人票,现在给定每个人买单人票各自所需的时间,或者相邻的人买双人票所需的时间,问最早什么时候能回去(8点开始排队)
思路
状态为dp[i],代表到第i个人时所需的最少时间,那么状态转移方程为dp[i] = min(dp[i - 1] + a[i], dp[i - 2] + b[i]),其中a为买单人票的时间数组,b为买双人票的时间数组

#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 2e3 + 15;

int a[N], b[N], dp[N];

int main(){
	int t;
	scanf("%d", &t);
	while(t--){
		int n;
		scanf("%d", &n);
		for(int i = 1; i <= n; i++)  scanf("%d", &a[i]);
		for(int i = 2; i <= n; i++)  scanf("%d", &b[i]);
	  dp[0] = 0, dp[1] = a[1];

	  for(int i = 2; i <= n; i++){
	  	dp[i] = min(dp[i - 1] + a[i], dp[i - 2] + b[i]);
	  }

	  int h = dp[n]/3600;
	  dp[n] %= 3600;
	  int m = dp[n]/60;
	  dp[n] %= 60;
	  int s = dp[n];
	  printf("%02d:%02d:%02d am\n", h + 8, m, s);
	}
}


铺地砖 HRBUST - 2186

思路
解法一:状态转移方程为dp[i] = dp[i - 2]*3 + dp[4]*2 + dp[6]*2 + ... + dp[i - 4]*2,画图可知
解法二:通过OEIS查到原来有一条递推公式 dp[i] = 4*dp[i - 1] - dp[i - 2],可以更加高效的求解

	#include<cstdio>
	#include<algorithm>
	using namespace std;
	const int N = 2e3 + 15;
	typedef long long ll;

	ll dp[N];

	int main(){
		dp[0] = 1, dp[2] = 3;
		for(int i = 4; i <= 45; i += 2){
			dp[i] = dp[i - 2] * 3;
			for(int j = 4; j <= i; j += 2){
				dp[i] += dp[i - j] * 2;
			}
		}

		int n;
		while(~scanf("%d", &n)){
			if(n&1)    printf("%d\n", 0);
			else       printf("%lld\n", dp[n]);
		}
	}


Monkey and Banana - HDU - 1069

题意
给定n种砖,每种砖都有无限块,现在要垒砖,规定下面的砖的长和宽都必须严格大于上面的,问砖最多能垒多高
思路
LIS变形
首先输入一块砖变成六块砖,然后可列出状态转移方程 dp[i] = max(dp[1], dp[2], ..., dp[i - 1]) + a[i],其中dp为该砖作为最下面的砖时的h最大值,最后扫描一遍dp数组找最大

	#include <cstdio>
	#include <algorithm>
	#include <cstring>
	using namespace std;
	const int N =   200 + 15;
	const int inf = 0x7fffffff;

	struct Node {
	    int x, y, h;
	    Node(int px = 0, int py = 0, int ph = 0): x(px), y(py), h(ph) {}

	    bool operator < (const Node& b) const{   //>
	        if(x != b.x)    return x < b.x;
	        else            return y < b.y;
	    }
	};

	Node a[N];
	int  dp[N];

	int main(){
	    int n, csn = 0;
	    while(scanf("%d", &n) && n){
	        int tot = 0;
	        while(n--){
	            int x, y, z;
	            scanf("%d%d%d", &x, &y, &z);
	            a[++tot] = Node(x, y, z);
	            a[++tot] = Node(y, x, z);
	            a[++tot] = Node(x, z, y);
	            a[++tot] = Node(z, x, y);
	            a[++tot] = Node(y, z, x);
	            a[++tot] = Node(z, y, x);
	        }
	        sort(a + 1, a + tot + 1);

	        for(int i = 1; i <= tot; ++i){
	            int maxx = 0;
	            for(int j = 1; j < i; ++j){
	                if(a[j].x < a[i].x && a[j].y < a[i].y){
	                    maxx = max(maxx, dp[j]);
	                }
	            }
	            dp[i] = a[i].h + maxx;
	        }

	        int ans = 0;
	        for(int i = 1; i <= tot; ++i){
	            ans = max(ans, dp[i]);
	        }
	        printf("Case %d: maximum height = %d\n", ++csn, ans);
	    }
	}


FatMouse and Cheese - HDU - 1078

题意
从(0,0)点开始出发,每次最多向横或向纵走k步,且到达的位置中的数值必须严格大于当前位置中的数值,问数值之和最大为多少
思路
记忆化搜索(DFS + DP),与POJ滑雪那题相同

	#include <cstdio>
	#include <algorithm>
	#include <cstring>
	using namespace std;
	const int N =   100 + 15;
	const int inf = 0x7fffffff;

	int a[N][N], dp[N][N];

	int dpDfs(int x, int y, int n, int k){
	    if(dp[x][y])    return dp[x][y];

	    int maxx = 0;
	    for(int i = x - k; i <= x + k; i++){
	        if(i < 1 || i > n)          continue;
	        if(a[i][y] <= a[x][y])      continue;
	        maxx = max(maxx, dpDfs(i, y, n, k));
	    }
	    for(int j = y - k; j <= y + k; j++){
	        if(j < 1 || j > n)          continue;
	        if(a[x][j] <= a[x][y])      continue;
	        maxx = max(maxx, dpDfs(x, j, n, k));
	    }
	    dp[x][y] = a[x][y] + maxx;
	    return a[x][y] + maxx;
	}

	int main(){
	    int n, k;
	    while(scanf("%d%d", &n, &k) && n != -1 && k != -1){
	        memset(dp[0], 0, sizeof(dp));
	        for(int i = 1; i <= n; i++){
	            for(int j = 1; j <= n; j++){
	                scanf("%d", &a[i][j]);
	            }
	        }

	        printf("%d\n", dpDfs(1, 1, n, k));
	    }
	}


Phalanx HDU - 2859

题意
求矩阵中对称子矩阵阶数最大值
思路
定义dp为当前点作为左下角点所在矩阵阶数最大值,则dp[i][j] = dp[i - 1][j + 1] + 1dp[i][j] = 1,除了获取右上角dp值外,还要往上扫描和往右扫描看对应元素是否相同,相同则+1,不同则为1

	#include <cstdio>
	#include <algorithm>
	#include <cstring>
	using namespace std;
	const int N =   1000 + 15;
	const int inf = 0x7fffffff;

	char a[N][N];
	int  dp[N][N];

	int main(){
	    int n;
	    while(scanf("%d", &n) && n){
	        for(int i = 1; i <= n; ++i){
	            getchar();
	            for(int j = 1; j <= n; ++j){
	                a[i][j] = getchar();
	            }
	        }

	        int ans = 1;
	        for(int i = 1; i <= n; ++i){
	            for(int j = 1; j <= n; ++j){
	                dp[i][j] = 1;
	                if(i == 1 || j == n)    continue;

	                int maxk = dp[i - 1][j + 1];
	                int maxx = 1;
	                while(maxx <= maxk && a[i][j + maxx] == a[i - maxx][j])     ++maxx;
	                dp[i][j] = maxx;
	                ans = max(ans, maxx);
	            }
	        }

	        printf("%d\n", ans);
	    }
	}