Uva 1213 - Sum of Different Primes(DP)

题目链接 https://vjudge.net/problem/UVA-1213

【题意】

    给定两个正整数n,  k (n<=1120,k<=14), 求用k个互不相同的素数凑出和为n的方案总数

【思路】

    先要用埃氏筛选打出[0,1120]内的素数表,下一步就是动态规划了,这个问题类似于01背包问题,设dp(k,i,j)表示在前面k个素数中选出j个素数使他们的和为i的方案数。状态转移方程也类似于01背包,如下所示:

    dp(k+1,i,j)=dp(k,i,j) (其它情况)

                    =dp(k,i-prime[k],j-1) (i>=prime[k]&&j-1>=0)

    然后按照这个递推式求解即可,注意边界条件, 当k=0时,dp[0][0][0]=1, dp[0][i][j]=0,当i=0,j=0时dp[k][0][0]=0. 同时还要注意的是循环的内外层关系,通过观察上面的递推公式可知应当现将k从小到大枚举,然后j从小到达枚举,最后是i

    最后,这个dp数组可以和01背包问题一样进行状态压缩,将第一维的k去掉,因为我们最后只关心从1120以内所有的素数中去取素数而不是从前面的若干项中去取素数,可以节约内存空间。

#include<bits/stdc++.h>
using namespace std;

const int maxn = 1500;

int num;
bool isprime[maxn];
vector<int> prime;

void init() {
	fill(isprime, isprime + maxn, 1);
	isprime[0] = isprime[1] = 0;
	for (int i = 2; i < maxn; ++i) {
		if (isprime[i]) {
			prime.push_back(i);
			for (int j = i + i; j < maxn; j += i) isprime[j] = 0;
		}
	}
	num = prime.size();
}

int dp[maxn][maxn][15];

void solve() {
	memset(dp, 0, sizeof(dp));
	dp[0][0][0] = 1;
	for (int k = 0; k < num; ++k) {
		for (int j = 0; j < 15; ++j) {
			for (int i = 0; i < maxn; ++i) {
				if (j == 0 && i == 0) {
					dp[k + 1][i][j] = 1;
					continue;
				}
				dp[k + 1][i][j] = dp[k][i][j];
				if (i >= prime[k] && j - 1 >= 0) dp[k + 1][i][j] += dp[k][i - prime[k]][j - 1];
			}
		}
	}
}

int main() {
	int n, k;
	init();
	solve();
	while (scanf("%d%d", &n, &k) && (n + k)) {
		int ans = dp[num][n][k];
		printf("%d\n", ans);
	}
	return 0;
}
优化代码


#include<bits/stdc++.h>
using namespace std;

const int maxn = 1500;

int num;
vector<int> p;//素数表
bool isp[maxn];
int dp[maxn][15];

void init() {//筛选素数
	fill(isp, isp + maxn, 1);
	isp[0] = isp[1] = 0;
	for (int i = 2; i < maxn; ++i) {
		if (isp[i]) {
			p.push_back(i);
			for (int j = i * 2; j < maxn; j += i) isp[j] = 0;
		}
	}
	num = p.size();
}

void solve() {//dp求解
	dp[0][0] = 1;
	for (int k = 0; k < num; ++k) {
		for (int j = 14; j >= 1; --j) {
			for (int i = maxn - 1; i - p[k] >= 0; --i) {
				/*
				注意这两个内层循环一定要倒着写,比如针对j而言,推算dp[i][j]时要用dp[i-p[k]][j-1]也就是j-1的结果,
				如果不这样写,循环从小往大,那么我们就会先更新dp[i-p[k]][j-1],等到更新dp[i][j]时,dp[i-p[k]][j-1]
				已经不是我们需要的值了,对i也是同样的道理,这些都是通过观察递推方程得出的
				*/
				dp[i][j] += dp[i - p[k]][j - 1];
			}
		}
	}
}

int main() {
	init();
	solve();
	int n, k;
	while (scanf("%d%d", &n, &k) == 2 && (n + k)) {
		int ans = dp[n][k];
		printf("%d\n", ans);
	}
	return 0;
}



posted @ 2018-01-27 21:19  不想吃WA的咸鱼  阅读(143)  评论(0编辑  收藏  举报