题目链接:http://acm.sgu.ru/problem.php?contest=0&problem=116
题意好晦涩
给你一个不超过一万的数 问它最少可以用多少个“超级素数”来表示 使“超级素数”之和等于它 如果无法这样表示 输出0 否则 按非降序形式输出方案
数论部分就模板的问题 没什么说的
完全背包方面也很常规
说说【输出方案】
背包九讲的伪码给的是二维dp[]的方法
实际上稍加改动就可以用在一维数组上
用一个rec[]记录dp[]的当前状态是从哪个状态转移而来(即上一个状态)
通过两个状态之间的“跨度”来求得“这一跨越是通过选取哪个物品来实现的”
应该可以证明 这种做法一定可以通过逆推还原出方案
对于此题的非降序要求
因为我们的物品(超级素数)是升序排列的
所以可以证明 逆推出来的东西一定是非降序的
#include <cstdio> #include <cstdlib> #include <ctime> #include <iostream> #include <cmath> #include <cstring> #include <algorithm> #include <stack> #include <set> #include <queue> #include <vector> using namespace std; typedef long long ll; const int maxn = 35005; const int maxm = 10010; const int INF = 100000; int prim[maxn]; int s_prim[maxn]; bool vis[maxn]; int tot; int n; void work() { memset(vis, 0, sizeof(vis)); tot = 0; prim[tot++] = 2; int t = 10010; for(int i = 3; i <= t; i += 2) { if(vis[i]) continue; for(int j = i*i; j <= t; j += 2*i) vis[j] = true; prim[tot++] = i; } } int dp[maxm]; int rec[maxm]; int main() { //freopen("in.txt", "r", stdin); work(); int tot2 = 0; for(int i = 0; i < tot; i++) { if(prim[prim[i] - 1] > 10000) break; s_prim[tot2++] = prim[prim[i] - 1]; } int m; scanf("%d", &m); fill(dp, dp + 10010, INF); dp[0] = 0; for(int i = 0; i < tot2; i++) { for(int j = s_prim[i]; j <= m; j++) { if(dp[j-s_prim[i]] + 1 < dp[j]) { dp[j] = dp[j-s_prim[i]] + 1; rec[j] = j - s_prim[i]; } } } if(dp[m] == INF) { printf("0 "); return 0; } int pt = m; printf("%d ", dp[m]); while(pt != 0) { printf("%d", pt - rec[pt]); pt = rec[pt]; printf("%c", " "[pt == 0]); } return 0; }