SGU 116 Index of super-prime 数论+完全背包+输出方案

题目链接: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\n");
        return 0;
    }

    int pt = m;
    printf("%d\n", dp[m]);
    while(pt != 0)
    {
        printf("%d", pt - rec[pt]);
        pt = rec[pt];
        printf("%c", " \n"[pt == 0]);
    }

    return 0;
}

 

posted @ 2015-02-19 22:42  地鼠地鼠  阅读(183)  评论(0编辑  收藏  举报